js-repository

0

Описание

Реализация репозитория для работы с базами данных

Языки

  • JavaScript100%
README.md

@e22m4u/js-repository

npm version license

Реализация паттерна «Репозиторий» для работы с базами данных.

Содержание

Установка

Опционально устанавливается нужный адаптер.

адаптерописаниеустановка
memory
Виртуальная база в памяти процессавстроенный
mongodb
MongoDB - документо-ориентированная база данныхnpm

Импорт

Модуль поддерживает ESM и CommonJS стандарты.

ESM

CommonJS

Описание

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

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

  • Источник данных - определяет способ подключения к базе;
  • Модель - описывает структуру документа и связи к другим моделям;
  • Репозиторий - выполняет операции чтения и записи документов модели;

Пример

Пример демонстрирует создание экземпляра схемы, объявление источника данных и модели

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

Страна (country) ┌─────────────────────────┐ │ id: 1 │ │ name: "Russia" │ │ population: 143400000 │ └─────────────────────────┘

В следующем блоке определяется модель

city
со связью
belongsTo
к модели
country
из примера выше. Затем создается новый документ города, связанный с ранее созданной страной. После создания нового документа, выполняется запрос на извлечение данного города с включением связанной страны.

Страна (country) Город (city) ┌─────────────────────────┐ ┌─────────────────────────┐ │ id: 1 <───────────────│───┐ │ id: 1 │ │ name: "Russia" │ │ │ name: "Moscow" │ │ population: 143400000 │ └───│─ countryId: 1 │ └─────────────────────────┘ └─────────────────────────┘

Схема баз данных

Экземпляр класса

DatabaseSchema
(далее «Схема») хранит определения источников данных и моделей. Схема связывает модели с источниками данных и используется для получения репозиториев, которые выполняют операции чтения и записи.

Методы

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

Примеры

Импорт класса и создание экземпляра схемы.

Определение нового источника.

Определение новой модели.

Получение репозитория по названию модели.

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

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

defineDatasource
экземпляра
DatabaseSchema
.

Параметры

  • name: string
    уникальное название;
  • adapter: string
    выбранный адаптер;
  • параметры адаптера (если имеются);

Примеры

Определение нового источника.

Передача дополнительных параметров на примере MongoDB адаптера (установка).

Модель

Описывает структуру документа коллекции и связи к другим моделям. Определение новой модели выполняется методом

defineModel
экземпляра
DatabaseSchema
.

Параметры

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

Примеры

Определение модели со свойствами указанного типа.

Свойства

Параметр

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

Тип данных

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

Параметры

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

Параметр

unique

Если значением параметра

unique
является
true
или
"strict"
, то выполняется строгая проверка на уникальность. В этом режиме любое значение данного свойства не может быть представлено более одного раза.

  • unique: true | 'strict'
    строгая проверка на уникальность;
  • unique: 'sparse'
    исключить из проверки ложные значения;
  • unique: false | 'nonUnique'
    не проверять на уникальность (по умолчанию);

Режим

"sparse"
исключает из проверки на уникальность ложные значения с точки зрения JavaScript. Указанные ниже значения будут проигнорированы при проверке в данном режиме.

  • ""
    пустая строка;
  • 0
    число ноль;
  • false
    логическое отрицание;
  • undefined
    и
    null
    ;

В качестве значений параметра

unique
можно использовать предопределенные константы как эквивалент строковых значений
strict
,
sparse
и
nonUnique
.

  • PropertyUniqueness.STRICT
    ;
  • PropertyUniqueness.SPARSE
    ;
  • PropertyUniqueness.NON_UNIQUE
    ;

Примеры

Краткое определение свойств модели.

Расширенное определение свойств модели.

Фабричное значение по умолчанию. Возвращаемое значение функции будет определено в момент записи документа.

Репозиторий

Репозиторий выполняет операции чтения и записи данных определенной модели. Он выступает в роли посредника между бизнес-логикой приложения и базой данных.

Методы

Аргументы

  • id: number|string
    идентификатор (первичный ключ);
  • data: object
    данные документа (используется при записи);
  • where: object
    условия фильтрации (см. Фильтрация);
  • filter: object
    параметры выборки (см. Фильтрация);

Получение репозитория

Получить репозиторий можно с помощью метода

getRepository()
экземпляра
DatabaseSchema
. В качестве аргумента метод принимает название модели. Обязательным условием является наличие у модели определенного источника данных (
datasource
), так как репозиторий напрямую взаимодействует с базой данных через указанный в источнике адаптер.

При первом вызове

getRepository('myModel')
будет создан и сохранен новый экземпляр репозитория. Все последующие вызовы с тем же названием модели будут возвращать уже существующий экземпляр.

repository.create

Создает новый документ в коллекции на основе переданных данных. Возвращает созданный документ с присвоенным идентификатором.

Сигнатура:

Примеры

Создание нового документа.

Создание документа с возвратом определенных полей.

Создание документа с включением связанных данных в результат.

repository.replaceById

Полностью заменяет существующий документ по его идентификатору. Все предыдущие данные документа, кроме идентификатора, удаляются. Поля, которые не были переданы в

data
, будут отсутствовать в итоговом документе (если для них не задано значение по умолчанию).

Сигнатура:

Примеры

Замена документа по идентификатору.

repository.replaceOrCreate

Заменяет существующий документ, если в переданных данных присутствует идентификатор, который уже существует в коллекции. В противном случае, если идентификатор не указан или не найден, создает новый документ.

Сигнатура:

Примеры

Создание нового документа, если

id: 3
не существует.

Замена существующего документа с

id: 1
.

repository.patchById

Частично обновляет существующий документ по его идентификатору, изменяя только переданные поля. Остальные поля документа остаются без изменений.

Сигнатура:

Примеры

Частичное обновление документа по идентификатору.

repository.patch

Частично обновляет один или несколько документов, соответствующих условиям

where
. Изменяются только переданные поля, остальные остаются без изменений. Возвращает количество обновленных документов. Если
where
не указан, обновляет все документы в коллекции.

Сигнатура:

Примеры

Обновление документов по условию.

Обновление всех документов.

repository.find

Находит все документы, соответствующие условиям фильтрации, и возвращает их в виде массива. Если фильтр не указан, возвращает все документы коллекции.

Сигнатура:

Примеры

Поиск всех документов.

Поиск документов по условию

where
.

Поиск с сортировкой и ограничением выборки.

repository.findOne

Находит первый документ, соответствующий условиям фильтрации. Возвращает

undefined
, если документы не найдены.

Сигнатура:

Примеры

Поиск одного документа по условию.

Обработка случая, когда документ не найден.

repository.findById

Поиск документа по идентификатору. Если документ не найден, выбрасывается ошибка.

Сигнатура:

Примеры

Поиск документа по

id
.

Поиск документа с включением связанных данных.

repository.delete

Удаляет один или несколько документов, соответствующих условиям

where
. Возвращает количество удаленных документов. Если
where
не указан, удаляет все документы в коллекции.

Сигнатура:

Примеры

Удаление документов по условию.

Удаление всех документов.

repository.deleteById

Удаляет один документ по его уникальному идентификатору. Возвращает

true
, если документ был найден и удален, в противном случае
false
.

Сигнатура:

Примеры

Удаление документа по

id
.

repository.exists

Проверяет существование документа с указанным идентификатором. Возвращает

true
, если документ существует, иначе
false
.

Сигнатура:

Примеры

Проверка существования документа по

id
.

repository.count

Подсчитывает количество документов, соответствующих условиям

where
. Если
where
не указан, возвращает общее количество документов в коллекции.

Сигнатура:

Примеры

Подсчет документов по условию.

Подсчет всех документов.

Фильтрация

Некоторые методы репозитория принимают объект настроек, влияющий на возвращаемый результат. Максимально широкий набор таких настроек имеет первый параметр метода

find
, где ожидается объект содержащий набор опций указанных ниже.

  • where: object
    условия фильтрации по свойствам документа;
  • order: string|string[]
    сортировка по указанным свойствам;
  • limit: number
    ограничение количества документов;
  • skip: number
    пропуск документов (пагинация);
  • fields: string|string[]
    выбор необходимых свойств модели;
  • include: object
    включение связанных данных в результат;

Пример

where

Параметр принимает объект с условиями выборки и поддерживает следующий набор операторов сравнения.

  • Поиск по значению
  • eq
    строгое равенство;
  • neq
    неравенство;
  • gt
    больше чем;
  • lt
    меньше чем;
  • gte
    больше или равно;
  • lte
    меньше или равно;
  • inq
    в списке;
  • nin
    не в списке;
  • between
    диапазон;
  • exists
    наличие свойства;
  • like
    SQL-подобный шаблон;
  • nlike
    исключающий шаблон;
  • ilike
    регистронезависимый шаблон;
  • nilike
    регистронезависимый шаблон исключения;
  • regexp
    регулярное выражение;

Условия можно объединять логическими операторами:

  • and
    логическое И;
  • or
    логическое ИЛИ;

Поиск по значению (сокращенная форма)

Находит документы, у которых значение указанного свойства в точности равно переданному значению. Это сокращенная запись для оператора

{eq: ...}
.

eq
(строгое равенство)

Находит документы, у которых значение свойства равно указанному.

neq
(неравенство)

Находит документы, у которых значение свойства не равно указанному.

gt
(больше чем)

Находит документы, у которых значение свойства строго больше указанного.

lt
(меньше чем)

Находит документы, у которых значение свойства строго меньше указанного.

gte
(больше или равно)

Находит документы, у которых значение свойства больше или равно указанному.

lte
(меньше или равно)

Находит документы, у которых значение свойства меньше или равно указанному.

inq
(в списке)

Находит документы, у которых значение свойства совпадает с одним из значений в предоставленном массиве.

nin
(не в списке)

Находит документы, у которых значение свойства отсутствует в предоставленном массиве.

between
(диапазон)

Находит документы, у которых значение свойства находится в указанном диапазоне (включая границы).

exists
(наличие свойства)

Проверяет наличие или отсутствие свойства в документе. Не проверяет значение свойства.

  • true
    свойство должно существовать (даже если его значение
    null
    );
  • false
    свойство должно отсутствовать;

like
(шаблон)

Выполняет сопоставление с шаблоном, с учетом регистра (см. подробнее).

nlike
(исключающий шаблон)

Находит документы, которые не соответствуют шаблону, с учетом регистра (см. подробнее).

ilike
(регистронезависимый шаблон)

Выполняет сопоставление с шаблоном без учета регистра (см. подробнее).

nilike
(регистронезависимый шаблон исключения)

Находит строки, которые не соответствуют шаблону, без учета регистра (см. подробнее).

regexp
(регулярное выражение)

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

RegExp
.

and
(логическое И)

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

or
(логическое ИЛИ)

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

Операторы сопоставления с шаблоном

Операторы

like
,
nlike
,
ilike
,
nilike
предназначены для фильтрации строковых свойств на основе сопоставления с шаблоном, подобно оператору
LIKE
в SQL. Они позволяют находить значения, которые соответствуют определённой структуре, используя специальные символы.

%
соответствует любой последовательности из нуля или более символов:

  • 'А%'
    найдет все строки, начинающиеся на "А";
  • '%а'
    найдет все строки, заканчивающиеся на "а";
  • '%слово%'
    найдет все строки, содержащие "слово" в любом месте;

_
соответствует ровно одному любому символу:

  • 'к_т'
    найдет "кот", "кит", но не "крот" или "кт";
  • 'кот_'
    найдет "коты", "коту" и "кота", но не "кот" или "котов";

Если нужно найти сами символы

%
или
_
как часть строки, их необходимо экранировать с помощью обратного слэша
\
:

  • '100\%'
    найдет строку "100%";
  • 'file\_name'
    найдет строку "file_name";
  • 'path\\to'
    найдет строку "path\to";

order

Параметр упорядочивает выборку по указанным свойствам модели. Обратное направление порядка можно задать постфиксом

DESC
в названии свойства.

Примеры

Упорядочить по полю

createdAt

Упорядочить по полю

createdAt
в обратном порядке.

Упорядочить по нескольким свойствам в разных направлениях.

i. Направление порядка

ASC
указывать необязательно.

include

Параметр включает связанные документы в результат вызываемого метода. Названия включаемых связей должны быть определены в текущей модели. (см. Связи)

Примеры

Включение связи по названию.

Включение вложенных связей.

Включение нескольких связей массивом.

Использование фильтрации включаемых документов.

Связи

Связи позволяют описывать отношения между моделями, что дает возможность автоматически встраивать связанные данные с помощью опции

include
в методах репозитория. Ниже приводится пример автоматического разрешения связи при использовании метода
findById
.

Роль (role) ┌────────────────────┐ │ id: 3 <──────────│────┐ │ name: 'Manager' │ │ └────────────────────┘ │ │ Пользователь (user) │ ┌────────────────────────┐ │ │ id: 1 │ │ │ name: 'John Doe' │ │ │ roleId: 3 ──────────│────┘ │ cityId: 24 ──────────│────┐ └────────────────────────┘ │ │ Город (city) │ ┌────────────────────┐ │ │ id: 24 <─────────│────┘ │ name: 'Moscow' │ └────────────────────┘

Определение связи

Свойство

relations
в определении модели принимает объект, ключи которого являются названиями связей, а значения их параметрами. В дальнейшем название связи можно будет использовать в опции
include
методах репозитория.

Основные параметры

  • type: string
    тип связи (обязательно);
  • model: string
    название целевой модели (обязательно для некоторых типов);
  • foreignKey: string
    свойство текущей модели для идентификатора цели;

i. Для типов Belongs To и References Many значение параметра

foreignKey
можно опустить, так как генерируется автоматически по названию связи.

Полиморфный режим

  • polymorphic: boolean|string
    объявление полиморфной связи;
  • discriminator: string
    свойство текущей модели для названия цели;

i. Полиморфный режим позволяет динамически определять целевую модель по ее названию, которое хранит документ в свойстве-дискриминаторе.

Типы связей

  • Belongs To
    Текущая модель ссылается на целевую по идентификатору.

    type: "belongsTo"
    или
    type: RelationType.BELONGS_TO

  • Has One
    Обратная сторона

    belongsTo
    по принципу "один к одному".
    type: "hasOne"
    или
    type: RelationType.HAS_ONE

  • Has Many
    Обратная сторона

    belongsTo
    по принципу "один ко многим".
    type: "hasMany"
    или
    type: RelationType.HAS_MANY

  • References Many
    Текущая модель ссылается на целевую через массив идентификаторов.

    type: "referencesMany"
    или
    type: RelationType.REFERENCES_MANY

Полиморфные версии:

Параметр

type
в определении связи принимает строку с названием типа. Чтобы исключить опечатку, рекомендуется использовать константы объекта
RelationType
указанные ниже.

  • RelationType.BELONGS_TO
  • RelationType.HAS_ONE
  • RelationType.HAS_MANY
  • RelationType.REFERENCES_MANY

Belongs To

Текущая модель ссылается на целевую по идентификатору.

Текущая (user) Целевая (role) ┌─────────────────────────┐ ┌─────────────────────────┐ │ id: 1 │ ┌───│─> id: 5 │ │ roleId: 5 ───────────│───┤ │ ... │ │ ... │ │ └─────────────────────────┘ └─────────────────────────┘ │ ┌─────────────────────────┐ │ │ id: 2 │ │ │ roleId: 5 ───────────│───┘ │ ... │ └─────────────────────────┘

Определение связи:

Пример:

Has One

Обратная сторона

belongsTo
по принципу "один к одному".

Текущая (profile) Целевая (user) ┌─────────────────────────┐ ┌─────────────────────────┐ │ id: 5 <──────────────│───┐ │ id: 1 │ │ ... │ └───│── profileId: 5 │ └─────────────────────────┘ │ ... │ └─────────────────────────┘

Определение связи:

Has Many

Обратная сторона

belongsTo
по принципу "один ко многим".

Текущая (role) Целевая (user) ┌─────────────────────────┐ ┌─────────────────────────┐ │ id: 5 <──────────────│───┐ │ id: 1 │ │ ... │ ├───│── roleId: 5 │ └─────────────────────────┘ │ │ ... │ │ └─────────────────────────┘ │ ┌─────────────────────────┐ │ │ id: 2 │ └───│── roleId: 5 │ │ ... │ └─────────────────────────┘

Определение связи:

References Many

Текущая модель ссылается на целевую через массив идентификаторов.

Текущая (article) Целевая (category) ┌─────────────────────────┐ ┌─────────────────────────┐ │ id: 1 │ ┌───│─> id: 5 │ │ categoryIds: [5, 6] ──│───┤ │ ... │ │ ... │ │ └─────────────────────────┘ └─────────────────────────┘ │ ┌─────────────────────────┐ └───│─> id: 6 │ │ ... │ └─────────────────────────┘

Определение связи:

Belongs To (полиморфная версия)

Текущая модель ссылается на целевую по идентификатору. Название целевой модели определяется свойством-дискриминатором.

Текущая (file) ┌──────> Целевая 1 (letter) ┌─────────────────────────────┐ │ ┌─────────────────────────┐ │ id: 1 │ │ ┌──│─> id: 10 │ │ referenceType: 'letter' ─│──┘ │ │ ... │ │ referenceId: 10 ─────────│────┘ └─────────────────────────┘ └─────────────────────────────┘ ┌──────> Целевая 2 (user) ┌─────────────────────────────┐ │ ┌─────────────────────────┐ │ id: 2 │ │ ┌──│─> id: 5 │ │ referenceType: 'user' ───│──┘ │ │ ... │ │ referenceId: 5 ──────────│────┘ └─────────────────────────┘ └─────────────────────────────┘

Определение связи:

Определение связи с указанием свойств:

Has One (полиморфная версия)

Обратная сторона полиморфная связи

belongsTo
по принципу "один к одному".

Текущая (company) <───────┐ Целевая (license) ┌─────────────────────────┐ │ ┌─────────────────────────┐ │ id: 10 <─────────────│──┐ │ │ id: 1 │ │ ... │ │ └──│── ownerType: 'company' │ └─────────────────────────┘ └────│── ownerId: 10 │ └─────────────────────────┘

Определение связи с указанием названия связи целевой модели:

Определение связи с указанием свойств целевой модели:

Has Many (полиморфная версия)

Обратная сторона полиморфная связи

belongsTo
по принципу "один ко многим".

Текущая (letter) <─────────┐ Целевая (file) ┌──────────────────────────┐ │ ┌────────────────────────────┐ │ id: 10 <──────────────│──┐ │ │ id: 1 │ │ ... │ │ ├──│── referenceType: 'letter' │ └──────────────────────────┘ ├─│──│── referenceId: 10 │ │ │ └────────────────────────────┘ │ │ ┌────────────────────────────┐ │ │ │ id: 2 │ │ └──│── referenceType: 'letter' │ └────│── referenceId: 10 │ └────────────────────────────┘

Определение связи с указанием названия связи целевой модели:

Определение связи с указанием свойств целевой модели:

Расширение

Метод

getRepository
экземпляра
DatabaseSchema
проверяет наличие существующего репозитория для указанной модели и возвращает его. В противном случае создается новый экземпляр, который будет сохранен для последующих обращений к методу.

Подмена стандартного конструктора репозитория выполняется методом

setRepositoryCtor
сервиса
RepositoryRegistry
, который находится в сервис-контейнере экземпляра
DatabaseSchema
. После чего все новые репозитории будут создаваться указанным конструктором вместо стандартного.

i. Так как экземпляры репозитория кэшируется, то замену конструктора следует выполнять до обращения к методу

getRepository
.

TypeScript

Получение типизированного репозитория с указанием интерфейса модели.

Тесты

Лицензия

MIT