js-repository
Описание
Реализация репозитория для работы с базами данных
Языки
- JavaScript100%
@e22m4u/js-repository
Реализация паттерна «Репозиторий» для работы с базами данных.
Содержание
- Установка
- Импорт
- Описание
- Пример
- Схема баз данных
- Источник данных
- Модель
- Свойства
- Репозиторий
- Фильтрация
- Связи
- Расширение
- TypeScript
- Тесты
- Лицензия
Установка
Опционально устанавливается нужный адаптер.
| адаптер | описание | установка |
|---|---|---|
| Виртуальная база в памяти процесса | встроенный |
| MongoDB - документо-ориентированная база данных | npm |
Импорт
Модуль поддерживает ESM и CommonJS стандарты.
ESM
CommonJS
Описание
Модуль позволяет абстрагироваться от различных интерфейсов баз данных, представляя их как именованные источники данных, подключаемые к моделям. Модель же описывает таблицу базы, колонки которой являются свойствами модели. Свойства модели могут иметь определенный тип допустимого значения. Кроме того, модель может определять классические связи «один к одному», «один ко многим» и другие типы отношений между моделями.
Непосредственно чтение и запись данных производится с помощью репозитория, который есть у каждой модели с объявленным источником данных. Репозиторий может фильтровать запрашиваемые документы, выполнять валидацию свойств согласно определению модели, и встраивать связанные данные в результат выборки.
- Источник данных - определяет способ подключения к базе;
- Модель - описывает структуру документа и связи к другим моделям;
- Репозиторий - выполняет операции чтения и записи документов модели;
Пример
Пример демонстрирует создание экземпляра схемы, объявление источника данных
и модели . После чего, с помощью репозитория данной модели, в коллекцию
добавляется новый документ (страна), который выводится в консоль.
Страна (country)
┌─────────────────────────┐
│ id: 1 │
│ name: "Russia" │
│ population: 143400000 │
└─────────────────────────┘
В следующем блоке определяется модель со связью к модели
из примера выше. Затем создается новый документ города, связанный
с ранее созданной страной. После создания нового документа, выполняется запрос
на извлечение данного города с включением связанной страны.
Страна (country) Город (city)
┌─────────────────────────┐ ┌─────────────────────────┐
│ id: 1 <───────────────│───┐ │ id: 1 │
│ name: "Russia" │ │ │ name: "Moscow" │
│ population: 143400000 │ └───│─ countryId: 1 │
└─────────────────────────┘ └─────────────────────────┘
Схема баз данных
Экземпляр класса (далее «Схема») хранит определения
источников данных и моделей. Схема связывает модели с источниками данных
и используется для получения репозиториев, которые выполняют операции
чтения и записи.
Методы
- добавить источник;defineDatasource(datasourceDef: object): this- добавить модель;defineModel(modelDef: object): this- получить репозиторий;getRepository(modelName: string): Repository
Примеры
Импорт класса и создание экземпляра схемы.
Определение нового источника.
Определение новой модели.
Получение репозитория по названию модели.
Источник данных
Источник хранит название выбранного адаптера и его настройки. Определение
нового источника выполняется методом экземпляра
.
Параметры
уникальное название;name: stringвыбранный адаптер;adapter: string- параметры адаптера (если имеются);
Примеры
Определение нового источника.
Передача дополнительных параметров на примере MongoDB адаптера (установка).
Модель
Описывает структуру документа коллекции и связи к другим моделям. Определение
новой модели выполняется методом экземпляра .
Параметры
название модели (обязательно);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'модель элемента массива (дляitemModel?: string);type: 'array'модель объекта (дляmodel?: string);type: 'object'объявить свойство первичным ключом;primaryKey?: booleanпереопределение названия колонки;columnName?: stringтип колонки (определяется адаптером);columnType?: stringобъявить свойство обязательным;required?: booleanзначение по умолчанию (заменяетdefault?: anyиundefined);nullпроверять значение на уникальность;unique?: boolean | string
Параметр
Если значением параметра является или , то выполняется
строгая проверка на уникальность. В этом режиме любое значение данного свойства
не может быть представлено более одного раза.
строгая проверка на уникальность;unique: true | 'strict'исключить из проверки ложные значения;unique: 'sparse'не проверять на уникальность (по умолчанию);unique: false | 'nonUnique'
Режим исключает из проверки на уникальность ложные значения с точки
зрения JavaScript. Указанные ниже значения будут проигнорированы при проверке
в данном режиме.
пустая строка;""число ноль;0логическое отрицание;falseиundefined;null
В качестве значений параметра можно использовать предопределенные
константы как эквивалент строковых значений , и .
;PropertyUniqueness.STRICT;PropertyUniqueness.SPARSE;PropertyUniqueness.NON_UNIQUE
Примеры
Краткое определение свойств модели.
Расширенное определение свойств модели.
Фабричное значение по умолчанию. Возвращаемое значение функции будет определено в момент записи документа.
Репозиторий
Репозиторий выполняет операции чтения и записи данных определенной модели. Он выступает в роли посредника между бизнес-логикой приложения и базой данных.
Методы
создать новый документ;create(data, filter = undefined)заменить документ полностью;replaceById(id, data, filter = undefined)заменить или создать новый;replaceOrCreate(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: number|stringданные документа (используется при записи);data: objectусловия фильтрации (см. Фильтрация);where: objectпараметры выборки (см. Фильтрация);filter: object
Получение репозитория
Получить репозиторий можно с помощью метода экземпляра
. В качестве аргумента метод принимает название модели.
Обязательным условием является наличие у модели определенного источника данных (), так как репозиторий напрямую взаимодействует
с базой данных через указанный в источнике адаптер.
При первом вызове будет создан и сохранен новый
экземпляр репозитория. Все последующие вызовы с тем же названием модели будут
возвращать уже существующий экземпляр.
repository.create
Создает новый документ в коллекции на основе переданных данных. Возвращает созданный документ с присвоенным идентификатором.
Сигнатура:
Примеры
Создание нового документа.
Создание документа с возвратом определенных полей.
Создание документа с включением связанных данных в результат.
repository.replaceById
Полностью заменяет существующий документ по его идентификатору. Все предыдущие
данные документа, кроме идентификатора, удаляются. Поля, которые не были
переданы в , будут отсутствовать в итоговом документе (если для них
не задано значение по умолчанию).
Сигнатура:
Примеры
Замена документа по идентификатору.
repository.replaceOrCreate
Заменяет существующий документ, если в переданных данных присутствует идентификатор, который уже существует в коллекции. В противном случае, если идентификатор не указан или не найден, создает новый документ.
Сигнатура:
Примеры
Создание нового документа, если не существует.
Замена существующего документа с .
repository.patchById
Частично обновляет существующий документ по его идентификатору, изменяя только переданные поля. Остальные поля документа остаются без изменений.
Сигнатура:
Примеры
Частичное обновление документа по идентификатору.
repository.patch
Частично обновляет один или несколько документов, соответствующих условиям
. Изменяются только переданные поля, остальные остаются без изменений.
Возвращает количество обновленных документов. Если не указан,
обновляет все документы в коллекции.
Сигнатура:
Примеры
Обновление документов по условию.
Обновление всех документов.
repository.find
Находит все документы, соответствующие условиям фильтрации, и возвращает их в виде массива. Если фильтр не указан, возвращает все документы коллекции.
Сигнатура:
Примеры
Поиск всех документов.
Поиск документов по условию .
Поиск с сортировкой и ограничением выборки.
repository.findOne
Находит первый документ, соответствующий условиям фильтрации. Возвращает
, если документы не найдены.
Сигнатура:
Примеры
Поиск одного документа по условию.
Обработка случая, когда документ не найден.
repository.findById
Поиск документа по идентификатору. Если документ не найден, выбрасывается ошибка.
Сигнатура:
Примеры
Поиск документа по .
Поиск документа с включением связанных данных.
repository.delete
Удаляет один или несколько документов, соответствующих условиям .
Возвращает количество удаленных документов. Если не указан, удаляет
все документы в коллекции.
Сигнатура:
Примеры
Удаление документов по условию.
Удаление всех документов.
repository.deleteById
Удаляет один документ по его уникальному идентификатору. Возвращает ,
если документ был найден и удален, в противном случае .
Сигнатура:
Примеры
Удаление документа по .
repository.exists
Проверяет существование документа с указанным идентификатором. Возвращает
, если документ существует, иначе .
Сигнатура:
Примеры
Проверка существования документа по .
repository.count
Подсчитывает количество документов, соответствующих условиям . Если
не указан, возвращает общее количество документов в коллекции.
Сигнатура:
Примеры
Подсчет документов по условию.
Подсчет всех документов.
Фильтрация
Некоторые методы репозитория принимают объект настроек, влияющий
на возвращаемый результат. Максимально широкий набор таких настроек
имеет первый параметр метода , где ожидается объект содержащий
набор опций указанных ниже.
условия фильтрации по свойствам документа;where: objectсортировка по указанным свойствам;order: string|string[]ограничение количества документов;limit: numberпропуск документов (пагинация);skip: numberвыбор необходимых свойств модели;fields: string|string[]включение связанных данных в результат;include: object
Пример
where
Параметр принимает объект с условиями выборки и поддерживает следующий набор операторов сравнения.
- Поиск по значению
строгое равенство;eqнеравенство;neqбольше чем;gtменьше чем;ltбольше или равно;gteменьше или равно;lteв списке;inqне в списке;ninдиапазон;betweenналичие свойства;existsSQL-подобный шаблон;likeисключающий шаблон;nlikeрегистронезависимый шаблон;ilikeрегистронезависимый шаблон исключения;nilikeрегулярное выражение;regexp
Условия можно объединять логическими операторами:
Поиск по значению (сокращенная форма)
Находит документы, у которых значение указанного свойства в точности равно
переданному значению. Это сокращенная запись для оператора .
eq (строгое равенство)
Находит документы, у которых значение свойства равно указанному.
neq (неравенство)
Находит документы, у которых значение свойства не равно указанному.
gt (больше чем)
Находит документы, у которых значение свойства строго больше указанного.
lt (меньше чем)
Находит документы, у которых значение свойства строго меньше указанного.
gte (больше или равно)
Находит документы, у которых значение свойства больше или равно указанному.
lte (меньше или равно)
Находит документы, у которых значение свойства меньше или равно указанному.
inq (в списке)
Находит документы, у которых значение свойства совпадает с одним из значений в предоставленном массиве.
nin (не в списке)
Находит документы, у которых значение свойства отсутствует в предоставленном массиве.
between (диапазон)
Находит документы, у которых значение свойства находится в указанном диапазоне (включая границы).
exists (наличие свойства)
Проверяет наличие или отсутствие свойства в документе. Не проверяет значение свойства.
свойство должно существовать (даже если его значениеtrue);nullсвойство должно отсутствовать;false
like (шаблон)
Выполняет сопоставление с шаблоном, с учетом регистра (см. подробнее).
nlike (исключающий шаблон)
Находит документы, которые не соответствуют шаблону, с учетом регистра (см. подробнее).
ilike (регистронезависимый шаблон)
Выполняет сопоставление с шаблоном без учета регистра (см. подробнее).
nilike (регистронезависимый шаблон исключения)
Находит строки, которые не соответствуют шаблону, без учета регистра (см. подробнее).
regexp (регулярное выражение)
Находит документы, у которых значение строкового свойства соответствует
указанному регулярному выражению. Может быть передано в виде строки или
объекта .
and (логическое И)
Объединяет несколько условий в массив, требуя, чтобы каждое условие было выполнено.
or (логическое ИЛИ)
Объединяет несколько условий в массив, требуя, чтобы хотя бы одно из них было выполнено.
Операторы сопоставления с шаблоном
Операторы , , , предназначены для фильтрации
строковых свойств на основе сопоставления с шаблоном, подобно оператору
в SQL. Они позволяют находить значения, которые соответствуют
определённой структуре, используя специальные символы.
соответствует любой последовательности из нуля или более символов:
найдет все строки, начинающиеся на "А";'А%'найдет все строки, заканчивающиеся на "а";'%а'найдет все строки, содержащие "слово" в любом месте;'%слово%'
соответствует ровно одному любому символу:
найдет "кот", "кит", но не "крот" или "кт";'к_т'найдет "коты", "коту" и "кота", но не "кот" или "котов";'кот_'
Если нужно найти сами символы или как часть строки, их необходимо
экранировать с помощью обратного слэша :
найдет строку "100%";'100\%'найдет строку "file_name";'file\_name'найдет строку "path\to";'path\\to'
order
Параметр упорядочивает выборку по указанным свойствам модели. Обратное
направление порядка можно задать постфиксом в названии свойства.
Примеры
Упорядочить по полю
Упорядочить по полю в обратном порядке.
Упорядочить по нескольким свойствам в разных направлениях.
i. Направление порядка указывать необязательно.
include
Параметр включает связанные документы в результат вызываемого метода. Названия включаемых связей должны быть определены в текущей модели. (см. Связи)
Примеры
Включение связи по названию.
Включение вложенных связей.
Включение нескольких связей массивом.
Использование фильтрации включаемых документов.
Связи
Связи позволяют описывать отношения между моделями, что дает возможность
автоматически встраивать связанные данные с помощью опции
в методах репозитория. Ниже приводится пример автоматического разрешения
связи при использовании метода .
Роль (role)
┌────────────────────┐
│ id: 3 <──────────│────┐
│ name: 'Manager' │ │
└────────────────────┘ │
│
Пользователь (user) │
┌────────────────────────┐ │
│ id: 1 │ │
│ name: 'John Doe' │ │
│ roleId: 3 ──────────│────┘
│ cityId: 24 ──────────│────┐
└────────────────────────┘ │
│
Город (city) │
┌────────────────────┐ │
│ id: 24 <─────────│────┘
│ name: 'Moscow' │
└────────────────────┘
Определение связи
Свойство в определении модели принимает объект, ключи которого
являются названиями связей, а значения их параметрами. В дальнейшем название
связи можно будет использовать в опции методах репозитория.
Основные параметры
тип связи (обязательно);type: stringназвание целевой модели (обязательно для некоторых типов);model: stringсвойство текущей модели для идентификатора цели;foreignKey: string
i. Для типов Belongs To и References Many значение параметра
можно опустить, так как генерируется автоматически по названию связи.
Полиморфный режим
объявление полиморфной связи;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
Полиморфные версии:
Параметр в определении связи принимает строку с названием типа. Чтобы исключить опечатку, рекомендуется использовать константы объекта
указанные ниже.
- 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
Обратная сторона по принципу "один к одному".
Текущая (profile) Целевая (user)
┌─────────────────────────┐ ┌─────────────────────────┐
│ id: 5 <──────────────│───┐ │ id: 1 │
│ ... │ └───│── profileId: 5 │
└─────────────────────────┘ │ ... │
└─────────────────────────┘
Определение связи:
Has Many
Обратная сторона по принципу "один ко многим".
Текущая (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 (полиморфная версия)
Обратная сторона полиморфная связи по принципу "один к одному".
Текущая (company) <───────┐ Целевая (license)
┌─────────────────────────┐ │ ┌─────────────────────────┐
│ id: 10 <─────────────│──┐ │ │ id: 1 │
│ ... │ │ └──│── ownerType: 'company' │
└─────────────────────────┘ └────│── ownerId: 10 │
└─────────────────────────┘
Определение связи с указанием названия связи целевой модели:
Определение связи с указанием свойств целевой модели:
Has Many (полиморфная версия)
Обратная сторона полиморфная связи по принципу "один ко многим".
Текущая (letter) <─────────┐ Целевая (file)
┌──────────────────────────┐ │ ┌────────────────────────────┐
│ id: 10 <──────────────│──┐ │ │ id: 1 │
│ ... │ │ ├──│── referenceType: 'letter' │
└──────────────────────────┘ ├─│──│── referenceId: 10 │
│ │ └────────────────────────────┘
│ │ ┌────────────────────────────┐
│ │ │ id: 2 │
│ └──│── referenceType: 'letter' │
└────│── referenceId: 10 │
└────────────────────────────┘
Определение связи с указанием названия связи целевой модели:
Определение связи с указанием свойств целевой модели:
Расширение
Метод экземпляра проверяет наличие
существующего репозитория для указанной модели и возвращает его.
В противном случае создается новый экземпляр, который будет сохранен
для последующих обращений к методу.
Подмена стандартного конструктора репозитория выполняется методом
сервиса , который находится
в сервис-контейнере экземпляра . После чего все новые
репозитории будут создаваться указанным конструктором вместо стандартного.
i. Так как экземпляры репозитория кэшируется, то замену конструктора
следует выполнять до обращения к методу .
TypeScript
Получение типизированного репозитория с указанием интерфейса модели.
Тесты
Лицензия
MIT