Абстракция для работы с базами данных для 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.ANYDataType.STRINGDataType.NUMBERDataType.BOOLEANDataType.ARRAYDataType.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)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)Добавляет новый документ в коллекцию.
Возвращает добавленный документ.
const person = await rep.create({
name: 'Rick Sanchez',
dimension: 'C-137',
age: 67,
});
console.log(person);
// {
// id: 1,
// name: 'Rick Sanchez',
// dimension: 'C-137',
// age: 67
// }
Заменяет существующий документ.
Возвращает затронутый документ.
// {
// id: 1,
// name: 'Rick Sanchez',
// dimension: 'C-137',
// age: 67
// }
const person = await rep.replaceById(1, {
name: 'Morty Smith',
kind: 'a young teenage boy',
age: 14,
});
console.log(person);
// {
// id: 1,
// name: 'Morty Smith',
// kind: 'a young teenage boy',
// age: 14
// }
Частично обновляет документы коллекции.
Возвращает число затронутых документов.
// [
// {
// "id": 1,
// "type": null,
// "name": "Bangkok"
// },
// {
// "id": 2,
// "type": null,
// "name": "Moscow"
// }
// ]
const result = await rep.patch({
type: 'city',
updatedAt: new Date().toISOString(),
});
console.log(result);
// 2
const docs = await rep.find();
console.log(docs);
// [
// {
// "id": 1,
// "type": "city",
// "name": "Bangkok",
// "updatedAt": "2023-12-02T14:13:27.649Z"
// },
// {
// "id": 2,
// "type": "city",
// "name": "Moscow",
// "updatedAt": "2023-12-02T14:13:27.649Z"
// }
// ]
Частично обновляет существующий документ.
Возвращает затронутый документ или выбрасывает исключение.
// {
// "id": 24,
// "type": "airport",
// "name": "Domodedovo Airport",
// "code": "DME"
// }
const result = await rep.patchById(24, {
name: 'Sheremetyevo Airport',
code: 'SVO',
});
console.log(result);
// {
// "id": 24,
// "type": "airport",
// "name": "Sheremetyevo Airport",
// "code": "SVO"
// }
Поиск по коллекции репозитория.
Возвращает найденные документы в виде массива.
// [
// {
// "id": 1,
// "title": "The Forgotten Ship"
// },
// {
// "id": 2,
// "title": "A Giant Bellows"
// },
// {
// "id": 3,
// "title": "Hundreds of bottles"
// }
// ]
const result = await rep.find();
console.log(result);
// [
// {
// "id": 1,
// "title": "The Forgotten Ship"
// },
// {
// "id": 2,
// "title": "A Giant Bellows"
// },
// {
// "id": 3,
// "title": "Hundreds of bottles"
// }
// ]
Поиск первого документа коллекции.
Возвращает найденный документ или undefined
const result = await rep.findOne();
console.log(result);
// {
// "id": 1,
// "title": "The Forgotten Ship",
// "tags": ['history', 'boat', 'cargo'],
// "type": "article"
// }
Поиск документа по идентификатору.
Возвращает найденный документ или выбрасывает исключение.
// [
// {
// "id": 1,
// "title": "The Forgotten Ship"
// },
// {
// "id": 2,
// "title": "A Giant Bellows"
// },
// {
// "id": 3,
// "title": "Hundreds of bottles"
// }
// ]
const result = await rep.findById(2);
console.log(result);
// {
// "id": 2,
// "title": "A Giant Bellows"
// }
Удаляет документы коллекции.
Возвращает количество удаленных документов.
// [
// {
// "id": 1,
// "title": "The Forgotten Ship"
// },
// {
// "id": 2,
// "title": "A Giant Bellows"
// },
// {
// "id": 3,
// "title": "Hundreds of bottles"
// }
// ]
const result = await rep.delete();
console.log(result);
// 3
const docs = await rep.find();
console.log(docs);
// []
Удаляет документ по идентификатору.
Возвращает true в случае успеха или false если
не найден.
// [
// {
// "id": 1,
// "title": "The Forgotten Ship"
// },
// {
// "id": 2,
// "title": "A Giant Bellows"
// },
// {
// "id": 3,
// "title": "Hundreds of bottles"
// }
// ]
const result = await rep.deleteById(2);
console.log(result);
// true
const docs = await rep.find();
console.log(docs);
// [
// {
// "id": 1,
// "title": "The Forgotten Ship"
// },
// {
// "id": 3,
// "title": "Hundreds of bottles"
// }
// ]
Проверка существования документа по идентификатору.
Возвращает true если найден, в противном случае
false.
// [
// {
// "id": 1,
// "title": "The Forgotten Ship"
// },
// {
// "id": 2,
// "title": "A Giant Bellows"
// },
// {
// "id": 3,
// "title": "Hundreds of bottles"
// }
// ]
const result1 = await rep.exists(2);
console.log(result1);
// true
const result2 = await rep.exists(10);
console.log(result2);
// false
Подсчет количества документов.
Возвращает число найденных документов.
// [
// {
// "id": 1,
// "title": "The Forgotten Ship"
// },
// {
// "id": 2,
// "title": "A Giant Bellows"
// },
// {
// "id": 3,
// "title": "Hundreds of bottles"
// }
// ]
const result = await rep.count();
console.log(result);
// 3
При использовании метода getRepository выполняется
проверка на наличие существующего экземпляра репозитория для нужной
модели. При первичном запросе создается новый экземпляр, который будет
сохранен для повторных обращений к методу.
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. После чего, все создаваемые репозитории
будут использовать уже пользовательский конструктор вместо
стандартного.
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
npm run test
MIT