Реализация репозитория для работы с базами данных
|
|
1 month ago | |
|---|---|---|
| .husky | 1 year ago | |
| assets | 1 year ago | |
| dist | 1 month ago | |
| src | 1 month ago | |
| .c8rc | 2 years ago | |
| .commitlintrc | 2 years ago | |
| .editorconfig | 2 years ago | |
| .gitignore | 1 year ago | |
| .mocharc.json | 4 months ago | |
| .prettierrc | 2 years ago | |
| LICENSE | 4 months ago | |
| README.md | 1 month ago | |
| build-cjs.js | 1 year ago | |
| eslint.config.js | 2 months ago | |
| package.json | 1 month ago | |
| tsconfig.json | 1 year ago |
Реализация паттерна «Репозиторий» для работы с базами данных в Node.js
npm install @e22m4u/js-repository
Опционально устанавливается нужный адаптер.
| адаптер | описание |
|---|---|
memory |
Виртуальная база в памяти процесса (не требует установки) |
mongodb |
MongoDB - система управления NoSQL базами (установка) |
Модуль поддерживает ESM и CommonJS стандарты.
ESM
import {DatabaseSchema} from '@e22m4u/js-repository';
CommonJS
const {DatabaseSchema} = require('@e22m4u/js-repository');
Модуль позволяет абстрагироваться от различных интерфейсов баз данных, представляя их как именованные источники данных, подключаемые к моделям. Модель же описывает таблицу базы, колонки которой являются свойствами модели. Свойства модели могут иметь определенный тип допустимого значения, набор валидаторов и трансформеров, через которые проходят данные перед записью в базу. Кроме того, модель может определять классические связи «один к одному», «один ко многим» и другие типы отношений между моделями.
Непосредственно чтение и запись данных производится с помощью репозитория, который есть у каждой модели с объявленным источником данных. Репозиторий может фильтровать запрашиваемые документы, выполнять валидацию свойств согласно определению модели, и встраивать связанные данные в результат выборки.
flowchart TD
A[Схема]
subgraph Базы данных
B[Источник данных 1]
C[Источник данных 2]
end
A-->B
A-->C
subgraph Коллекции
D[Модель A]
E[Модель Б]
F[Модель В]
G[Модель Г]
end
B-->D
B-->E
C-->F
C-->G
H[Репозиторий A]
I[Репозиторий Б]
J[Репозиторий В]
K[Репозиторий Г]
D-->H
E-->I
F-->J
G-->K
Пример демонстрирует создание экземпляра схемы, объявление источника
данных и модели country. После чего, с помощью репозитория
данной модели, в коллекцию добавляется новый документ (страна), который
выводится в консоль.
Страна (country)
┌─────────────────────────┐
│ id: 1 │
│ name: "Russia" │
│ population: 143400000 │
└─────────────────────────┘
import {DataType} from '@e22m4u/js-repository';
import {DatabaseSchema} from '@e22m4u/js-repository';
// создание экземпляра DatabaseSchema
const dbs = new DatabaseSchema();
// объявление источника "myDb"
dbs.defineDatasource({
name: 'myDb', // название нового источника
adapter: 'memory', // выбранный адаптер
});
// объявление модели "country"
dbs.defineModel({
name: 'country', // название новой модели
datasource: 'myDb', // выбранный источник
properties: { // свойства модели
name: DataType.STRING, // тип "string"
population: DataType.NUMBER, // тип "number"
},
})
// получение репозитория модели
const countryRep = dbs.getRepository('country');
// добавление нового документа в коллекцию
const country = await countryRep.create({
name: 'Russia',
population: 143400000,
});
// вывод нового документа
console.log(country);
// {
// id: 1,
// name: 'Russia',
// population: 143400000,
// }
В следующем блоке определяется модель city со связью
belongsTo к модели country из примера выше.
Затем создается новый документ города, связанный с ранее созданной
страной. После создания нового документа, выполняется запрос на
извлечение данного города с включением связанной страны.
Страна (country) Город (city)
┌─────────────────────────┐ ┌──────────────────────┐
│ id: 1 <───────────────│────┐ │ id: 1 │
│ name: "Russia" │ │ │ name: "Moscow" │
│ population: 143400000 │ └────│─ countryId: 1 │
└─────────────────────────┘ └──────────────────────┘
// объявление модели "city" со связью к "country"
dbs.defineModel({
name: 'city',
datasource: 'myDb',
properties: {
name: DataType.STRING,
countryId: DataType.NUMBER,
// внешний ключ "countryId" указывать не обязательно,
// но для проверки типа значения перед записью в базу
// рекомендуется, так как адаптер "memory" по умолчанию
// создает числовые идентификаторы
},
relations: {
// объявление связи "country" позволит автоматически включать
// связанные документы с помощью опции "include" при запросах
// из данной коллекции через методы репозитория
country: {
type: RelationType.BELONGS_TO, // тип связи: принадлежит к...
model: 'country', // название целевой модели
foreignKey: 'countryId', // поле с внешним ключом (не обязательно)
// если внешний ключ соответствует `relationName` + `Id`,
// то указывать опцию `foreignKey` не обязательно
},
},
});
// получение репозитория для модели "city"
const cityRep = dbs.getRepository('city');
// создание нового города и его привязка к стране через country.id
const city = await cityRep.create({
name: 'Moscow',
countryId: country.id, // использование id созданной ранее страны
});
console.log(city);
// {
// id: 1,
// name: 'Moscow',
// countryId: 1,
// }
// извлечение города по идентификатору с включением связанной страны
const cityWithCountry = await cityRep.findById(city.id, {
include: 'country',
});
console.log(cityWithCountry);
// {
// id: 1,
// name: 'Moscow',
// countryId: 1,
// country: {
// id: 1,
// name: 'Russia',
// population: 143400000
// }
// }
Экземпляр класса DatabaseSchema хранит определения
источников данных и моделей.
Методы
defineDatasource(datasourceDef: object): this -
добавить источникdefineModel(modelDef: object): this - добавить
модельgetRepository(modelName: string): Repository - получить
репозиторийПримеры
Импорт класса и создание экземпляра схемы.
import {DatabaseSchema} from '@e22m4u/js-repository';
const dbs = new DatabaseSchema();
Определение нового источника.
dbs.defineDatasource({
name: 'myDb', // название нового источника
adapter: 'memory', // выбранный адаптер
});
Определение новой модели.
dbs.defineModel({
name: 'product', // название новой модели
datasource: 'myDb', // выбранный источник
properties: { // свойства модели
name: DataType.STRING,
weight: DataType.NUMBER,
},
});
Получение репозитория по названию модели.
const productRep = dbs.getRepository('product');
Источник хранит название выбранного адаптера и его настройки.
Определение нового источника выполняется методом
defineDatasource экземпляра
DatabaseSchema.
Параметры
name: string уникальное названиеadapter: string выбранный адаптерПримеры
Определение нового источника.
dbs.defineDatasource({
name: 'myDb', // название нового источника
adapter: 'memory', // выбранный адаптер
});
Передача дополнительных параметров на примере MongoDB адаптера (установка).
dbs.defineDatasource({
name: 'myDb',
adapter: 'mongodb',
// параметры адаптера "mongodb"
host: '127.0.0.1',
port: 27017,
database: 'myDatabase',
});
Описывает структуру документа коллекции и связи к другим моделям.
Определение новой модели выполняется методом defineModel
экземпляра DatabaseSchema.
Параметры
name: string название модели (обязательно)base: string название наследуемой моделиtableName: string название коллекции в базеdatasource: string выбранный источник данныхproperties: object определения свойств (см. Свойства)relations: object определения связей (см. Связи)Примеры
Определение модели со свойствами указанного типа.
dbs.defineModel({
name: 'user', // название новой модели
properties: { // свойства модели
name: DataType.STRING,
age: DataType.NUMBER,
},
});
Параметр properties находится в определении модели и
принимает объект, ключи которого являются свойствами этой модели, а
значением тип свойства или объект с дополнительными параметрами.
Тип данных
DataType.ANY разрешено любое значениеDataType.STRING только значение типа
stringDataType.NUMBER только значение типа
numberDataType.BOOLEAN только значение типа
booleanDataType.ARRAY только значение типа
arrayDataType.OBJECT только значение типа
objectПараметры
type: string тип допустимого значения
(обязательно)itemType: string тип элемента массива (для
type: 'array')model: string модель объекта (для
type: 'object')primaryKey: boolean объявить свойство первичным
ключомcolumnName: string переопределение названия
колонкиcolumnType: string тип колонки (определяется
адаптером)required: boolean объявить свойство обязательнымdefault: any значение по умолчаниюvalidate: string | Function | array | object см. Валидаторыunique: boolean | string проверять значение на
уникальностьПараметр unique
Если значением параметра unique является
true или 'strict', то выполняется строгая
проверка на уникальность. В этом режиме пустые значения так же подлежат проверке,
где null и undefined также считаются
значениями, которые должны быть уникальными.
Режим 'sparse' проверяет только значения с полезной
нагрузкой, исключая пустые значения,
список которых отличается в зависимости от типа свойства. Например, для
типа string пустым значением будет undefined,
null и '' (пустая строка).
unique: true | 'strict' строгая проверка на
уникальностьunique: 'sparse' исключить из проверки пустые значенияunique: false | 'nonUnique' не проверять на
уникальность (по умолчанию)В качестве значений параметра unique можно использовать
предопределенные константы как эквивалент строковых значений
strict, sparse и nonUnique.
PropertyUniqueness.STRICTPropertyUniqueness.SPARSEPropertyUniqueness.NON_UNIQUEПримеры
Краткое определение свойств модели.
dbs.defineModel({
name: 'city',
properties: { // свойства модели
name: DataType.STRING, // тип свойства "string"
population: DataType.NUMBER, // тип свойства "number"
},
});
Расширенное определение свойств модели.
dbs.defineModel({
name: 'city',
properties: { // свойства модели
name: {
type: DataType.STRING, // тип свойства "string" (обязательно)
required: true, // исключение значений undefined и null
},
population: {
type: DataType.NUMBER, // тип свойства "number" (обязательно)
default: 0, // значение по умолчанию
},
code: {
type: DataType.NUMBER, // тип свойства "number" (обязательно)
unique: PropertyUniqueness.STRICT, // проверять уникальность
},
},
});
Фабричное значение по умолчанию. Возвращаемое значение функции будет определено в момент записи документа.
dbs.defineModel({
name: 'article',
properties: { // свойства модели
tags: {
type: DataType.ARRAY, // тип свойства "array" (обязательно)
itemType: DataType.STRING, // тип элемента "string"
default: () => [], // фабричное значение
},
createdAt: {
type: DataType.STRING, // тип свойства "string" (обязательно)
default: () => new Date().toISOString(), // фабричное значение
},
},
});
Валидаторы используются для проверки значения свойства перед записью в базу. Проверка значения валидатором выполняется сразу после проверки типа, указанного в определении свойства модели. Пустые значения пропускают проверку валидаторами, так как не имеют полезной нагрузки.
Модуль поставляется с набором глобальных валидаторов:
regexp проверка по регулярному выражению,
параметр: string | RegExp - регулярное
выражение;
maxLength максимальная длина строки или
массива,
параметр: number - максимальная длина;
minLength минимальная длина строки или
массива,
параметр: number - минимальная длина;
Валидаторы указанные ниже находятся в разработке:
isLowerCase проверка регистра (только строчные
буквы);isUpperCase проверка регистра (только прописные
буквы);isEmail проверка формата электронного адреса;Примеры
Использование глобального валидатора.
dbs.defineModel({
name: 'user',
properties: {
email: {
type: DataType.STRING,
validate: 'isEmail',
},
},
});
Использование глобальных валидаторов в виде массива.
dbs.defineModel({
name: 'user',
properties: {
email: {
type: DataType.STRING,
validate: [
'isEmail',
'isLowerCase',
],
},
},
});
Использование глобальных валидаторов с передачей аргументов.
dbs.defineModel({
name: 'user',
properties: {
name: {
type: DataType.STRING,
validate: {
minLength: 2,
maxLength: 24,
regexp: /^[a-zA-Z-']+$/,
},
},
},
});
Глобальные валидаторы без параметров могут принимать любые аргументы.
dbs.defineModel({
name: 'user',
properties: {
email: {
type: DataType.STRING,
validate: {
maxLength: 100,
// так как валидатор "isEmail" не имеет параметров,
// его определение допускает передачу любого значения
// в качестве аргумента
isEmail: true,
},
},
},
});
Валидатором является функция, в которую передается значение
соответствующего поля перед записью в базу. Если во время проверки
функция возвращает false, то выбрасывается стандартная
ошибка. Подмена стандартной ошибки возможна с помощью выброса
пользовательской ошибки непосредственно внутри проверяющей функции.
Регистрация глобального валидатора выполняется методом
addValidator сервиса
PropertyValidatorRegistry, который принимает название
валидатора и функцию для проверки значения.
Примеры
Регистрация глобального валидатора для проверки формата UUID.
import {createError} from 'http-errors';
import {format} from '@e22m4u/js-format';
import {Errorf} from '@e22m4u/js-format';
import {PropertyValidatorRegistry} from '@e22m4u/js-repository';
// получение экземпляра сервиса
const pvr = dbs.getService(PropertyValidatorRegistry);
// регулярные выражения для разных версий UUID
const uuidRegex = {
any: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
v4: /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
};
// регистрация глобального валидатора "isUuid",
// принимающего объект настроек со свойством "version"
pvr.addValidator('isUuid', (value, options, context) => {
// value - проверяемое значение;
// options - параметры валидатора;
// context - информация о проверяемом свойстве;
console.log(options);
// {
// version: 'v4'
// }
console.log(context);
// {
// validatorName: 'isUuid',
// modelName: 'device',
// propName: 'deviceId'
// }
// пустые значения не передаются в валидаторы
// (условие ниже никогда не сработает)
if (typeof value !== 'string') return false;
// поиск регулярного выражения для указанной
// версии UUID (из параметров валидатора)
const version = options?.version || 'any';
const regex = uuidRegex[version];
// если регулярное выражение не найдено,
// то выбрасывается внутренняя ошибка
if (!regex)
throw new Errorf(
'Invalid UUID version %v specified for validator.',
version,
);
// при неудачной проверке выбрасывается
// ошибка 400 BadRequest
if (!regex.test(value)) {
const versionString = version !== 'any' ? ` (version ${version})` : '';
throw createError(400, format(
'The property %v of the model %v must be a valid UUID%s.',
context.propName,
context.modelName,
versionString,
));
}
// при успешной проверке возвращается true,
// в противном случае выбрасывается стандартная
// ошибка проверки
return true;
});
Использование глобального валидатора в определении свойства.
// определение модели "device"
dbs.defineModel({
name: 'device',
properties: {
deviceId: {
type: DataType.STRING,
required: true,
validate: {
// значение {version: 'v4'} будет передаваться
// вторым аргументом в функцию-валидатор
isUuid: {version: 'v4'},
},
},
},
});
Функция-валидатор может быть передана непосредственно в определении
свойства без предварительной регистрации. Для этого достаточно передать
функцию в параметр validate в качестве значения или
элемента массива наряду с другими валидаторами.
Примеры
Использование локального валидатора для проверки сложности пароля.
// валидатор `passwordStrength` проверяет сложность пароля
function passwordStrength(value, options, context) {
// value - проверяемое значение;
// options - не используется;
// context - информация о свойстве;
console.log(context);
// {
// validatorName: 'passwordStrength',
// modelName: 'user',
// propName: 'password'
// }
const errors = [];
if (value.length < 8)
errors.push('must be at least 8 characters long');
if (!/\d/.test(value))
errors.push('must contain at least one number');
if (!/[a-zA-Z]/.test(value))
errors.push('must contain at least one letter');
// если одно из условий сработало,
// то выбрасывается ошибка
if (errors.length > 0)
throw createError(400, format(
'Value of the property %v of the model %v %s.',
context.propName,
context.modelName,
errors.join(', '),
));
// при успешной проверке возвращается true,
// в противном случае выбрасывается стандартная
// ошибка проверки
return true;
}
// определение модели "user"
dbs.defineModel({
name: 'user',
properties: {
password: {
type: DataType.STRING,
required: true,
validate: passwordStrength, // <=
// или
// validate: [passwordStrength, ...]
},
},
});
Использование анонимной функции-валидатора для проверки слага.
// определение модели "article"
dbs.defineModel({
name: 'article',
properties: {
slug: {
type: DataType.STRING,
validate: (value) => {
const re = /^[a-z0-9]+(-[a-z0-9]+)*$/;
return re.test(value);
},
},
},
});
Трансформеры используются для модификации значения свойства перед проверкой типа и передачей данных в базу. Трансформеры позволяют автоматически очищать или приводить данные к нужному формату. Пустые значения не передаются в трансформеры, так как не имеют полезной нагрузки.
Модуль поставляется с набором глобальных трансформеров:
trim удаление пробельных символов с начала и конца
строки;toUpperCase перевод строки в верхний регистр;toLowerCase перевод строки в нижний регистр;Трансформеры указанные ниже находятся в разработке:
cut усечение строки или массива до указанной
длины,
параметр: number - максимальная длина;
truncate усечение строки с добавлением
троеточия,
параметр: number - максимальная длина;
capitalize перевод первой буквы каждого слова в
верхний регистр,
параметр: {firstWordOnly?: boolean};
Примеры
Использование глобального трансформера.
dbs.defineModel({
name: 'user',
properties: {
username: {
type: DataType.STRING,
transform: 'toLowerCase',
},
},
});
Использование глобальных трансформеров в виде массива.
dbs.defineModel({
name: 'user',
properties: {
firstName: {
type: DataType.STRING,
transform: [
'trim',
'capitalize',
],
},
},
});
Использование глобальных трансформеров с передачей аргументов.
dbs.defineModel({
name: 'article',
properties: {
annotation: {
type: DataType.STRING,
transform: {
truncate: 200,
capitalize: {firstWordOnly: true},
},
},
},
});
Глобальные трансформеры без параметров могут принимать любые аргументы.
dbs.defineModel({
name: 'user',
properties: {
firstName: {
type: DataType.STRING,
transform: {
cut: 60,
// так как трансформер "trim" не имеет параметров,
// его определение допускает передачу любого значения
// в качестве аргумента
trim: true,
},
},
},
});
Трансформером является функция, которая принимает значение свойства и
возвращает новое значение. Функция может быть как синхронной, так и
асинхронной (возвращать Promise).
Регистрация глобального трансформера выполняется методом
addTransformer сервиса
PropertyTransformerRegistry, который принимает название
трансформера и саму функцию.
Примеры
Регистрация глобального трансформера для удаления HTML-тегов.
import {PropertyTransformerRegistry} from '@e22m4u/js-repository';
// получение экземпляра сервиса
const ptr = dbs.getService(PropertyTransformerRegistry);
// регистрация глобального трансформера "stripTags"
ptr.addTransformer('stripTags', (value, options, context) => {
// value - трансформируемое значение;
// options - настройки трансформера (если переданы);
// context - информация о свойстве;
console.log(context);
// {
// transformerName: 'stripTags',
// modelName: 'comment',
// propName: 'text'
// }
if (typeof value !== 'string')
return value; // возвращаем как есть, если не строка
return value.replace(/<[^>]*>?/gm, '');
});
Использование глобального трансформера в определении модели.
dbs.defineModel({
name: 'comment',
properties: {
text: {
type: DataType.STRING,
transform: 'stripTags',
},
},
});
Функция-трансформер может быть передана непосредственно в определении
свойства без предварительной регистрации. Для этого достаточно передать
функцию в параметр transform в качестве значения или
элемента массива.
Примеры
Использование локального трансформера для нормализации имен.
// функция для нормализации имени
function normalizeName(value, options, context) {
// value - трансформируемое значение
// options - не используется
// context - информация о свойстве
if (!value || typeof value !== 'string') return value;
return value
.trim() // удаление пробелов в начале и конце
.toLowerCase() // перевод к нижнему регистру
.split(' ') // разделение на слова
// перевод к верхнему регистру первой буквы каждого слова
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' '); // сборка массива в строку
}
// определение модели "user"
dbs.defineModel({
name: 'user',
properties: {
firstName: {
type: DataType.STRING,
transform: normalizeName, // <=
},
lastName: {
type: DataType.STRING,
transform: normalizeName, // <=
},
},
});
Использование локального асинхронного трансформера для хэширования пароля.
import * as bcrypt from 'bcrypt';
// асинхронная функция для хеширования значения
async function hash(value, options, context) {
// value - трансформируемое значение
// options - не используется
// context - информация о свойстве
console.log(context);
// {
// transformerName: 'hash',
// modelName: 'user',
// propName: 'password'
// }
const saltRounds = 10;
return bcrypt.hash(value, saltRounds);
}
// определение модели "user"
dbs.defineModel({
name: 'user',
properties: {
password: {
type: DataType.STRING,
transform: hash, // <=
// или
// transform: [hash, ...]
},
},
});
Использование анонимной функции-трансформера для коррекции слага.
dbs.defineModel({
name: 'article',
properties: {
slug: {
type: DataType.STRING,
transform: (value) => {
if (typeof value !== 'string') return value;
return value.toLowerCase().replace(/\s+/g, '-');
},
},
},
});
Разные типы свойств имеют свои наборы пустых значений. Эти наборы
используются для определения наличия полезной нагрузки в значении
свойства. Например, параметр default в определении свойства
устанавливает значение по умолчанию, только если входящее значение
является пустым. Параметр required исключает пустые
значения выбрасывая ошибку. А параметр unique в режиме
sparse наоборот допускает дублирование пустых значений
уникального свойства, поскольку они не участвуют в проверке.
| тип | пустые значения |
|---|---|
'any' |
undefined, null |
'string' |
undefined, null, '' |
'number' |
undefined, null, 0 |
'boolean' |
undefined, null |
'array' |
undefined, null, [] |
'object' |
undefined, null, {} |
Набор пустых значений для любого типа данных можно переопределить. Управление этими наборами осуществляется через специальный сервис, который предоставляет модуль @e22m4u/js-empty-values (не требует установки).
EmptyValuesService
Для переопределения пустых значений необходимо получить экземпляр
класса EmptyValuesService из контейнера схемы и вызвать
метод, который принимает тип данных и массив новых значений.
Интерфейс:
class EmptyValuesService {
/**
* Установить пустые значения
* для определенного типа данных.
*
* @param dataType Тип данных.
* @param emptyValues Массив новых пустых значений.
*/
setEmptyValuesOf(
dataType: DataType,
emptyValues: unknown[],
): this;
}
Пример
По умолчанию, для числовых свойств значение 0 считается
пустым. Следующий пример демонстрирует, как изменить это поведение,
оставив в качестве пустых значений только undefined и
null.
import {DataType} from '@e22m4u/js-repository';
import {DatabaseSchema} from '@e22m4u/js-repository';
import {EmptyValuesService} from '@e22m4u/js-empty-values';
const dbs = new DatabaseSchema();
// получение сервиса для работы с пустыми значениями
const emptyValuesService = dbs.getService(EmptyValuesService);
// переопределение пустых значений для типа DataType.NUMBER
emptyValuesService.setEmptyValuesOf(DataType.NUMBER, [undefined, null]);
После этого, значение 0 для свойств типа
DataType.NUMBER больше не будет считаться пустым и будет
проходить проверки валидаторами, а также не будет заменяться значением
по умолчанию.
Репозиторий выполняет операции чтения и записи данных определенной модели. Он выступает в роли посредника между бизнес-логикой приложения и базой данных.
Методы
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 параметры выборки (см. Фильтрация)Получение репозитория
Получить репозиторий можно с помощью метода
getRepository() экземпляра DatabaseSchema. В
качестве аргумента метод принимает название модели. Обязательным
условием является наличие у модели определенного источника данных (datasource),
так как репозиторий напрямую взаимодействует с базой данных через
указанный в источнике адаптер.
// объявление источника
dbs.defineDatasource({
name: 'myDatasource',
adapter: 'memory', // адаптер
});
// объявление модели
dbs.defineModel({
name: 'myModel',
datasource: 'myDatasource',
// properties: { ... },
// relations: { ... }
});
// получение репозитория модели
const modelRep = dbs.getRepository('myModel');
При первом вызове getRepository('myModel') будет создан
и сохранен новый экземпляр репозитория. Все последующие вызовы с тем же
названием модели будут возвращать уже существующий экземпляр.
Создает новый документ в коллекции на основе переданных данных. Возвращает созданный документ с присвоенным идентификатором.
Сигнатура:
create(
data: WithOptionalId<FlatData, IdName>,
filter?: ItemFilterClause<FlatData>,
): Promise<FlatData>;
Примеры
Создание нового документа.
const newProduct = await productRep.create({
name: 'Laptop',
price: 1200,
});
console.log(newProduct);
// {
// id: 1,
// name: 'Laptop',
// price: 1200,
// }
Создание документа с возвратом определенных полей.
const product = await productRep.create(
{name: 'Mouse', price: 25},
{fields: ['id', 'name']},
);
console.log(product);
// {
// id: 2,
// name: 'Mouse',
// }
Создание документа с включением связанных данных в результат.
// предполагается, что модель Product имеет связь "category"
// (опция "include" влияет только на возвращаемый результат)
const product = await productRep.create(
{name: 'Keyboard', price: 75, categoryId: 10},
{include: 'category'},
);
console.log(product);
// {
// id: 3,
// name: 'Keyboard',
// price: 75,
// categoryId: 10,
// category: {id: 10, name: 'Electronics'}
// }
Полностью заменяет существующий документ по его идентификатору. Все
предыдущие данные документа, кроме идентификатора, удаляются. Поля,
которые не были переданы в data, будут отсутствовать в
итоговом документе (если для них не задано значение по умолчанию).
Сигнатура:
replaceById(
id: IdType,
data: WithoutId<FlatData, IdName>,
filter?: ItemFilterClause<FlatData>,
): Promise<FlatData>;
Примеры
Замена документа по идентификатору.
// исходный документ
// {
// id: 1,
// name: 'Laptop',
// price: 1200,
// inStock: true
// }
const updatedProduct = await productRep.replaceById(1, {
name: 'Laptop Pro',
price: 1500,
});
console.log(updatedProduct);
// {
// id: 1,
// name: 'Laptop Pro',
// price: 1500
// }
// свойство "inStock" удалено
Заменяет существующий документ, если в переданных данных присутствует идентификатор, который уже существует в коллекции. В противном случае, если идентификатор не указан или не найден, создает новый документ.
Сигнатура:
replaceOrCreate(
data: WithOptionalId<FlatData, IdName>,
filter?: ItemFilterClause<FlatData>,
): Promise<FlatData>;
Примеры
Создание нового документа, если id: 3 не существует.
const product = await productRep.replaceOrCreate({
id: 3,
name: 'Keyboard',
price: 75,
});
console.log(product);
// {
// id: 3,
// name: 'Keyboard',
// price: 75,
// }
Замена существующего документа с id: 1.
const updatedProduct = await productRep.replaceOrCreate({
id: 1,
name: 'Laptop Pro',
price: 1500,
});
console.log(updatedProduct);
// {
// id: 1,
// name: 'Laptop Pro',
// price: 1500,
// }
Частично обновляет существующий документ по его идентификатору, изменяя только переданные поля. Остальные поля документа остаются без изменений.
Сигнатура:
patchById(
id: IdType,
data: PartialWithoutId<FlatData, IdName>,
filter?: ItemFilterClause<FlatData>,
): Promise<FlatData>;
Примеры
Частичное обновление документа по идентификатору.
// исходный документ с id: 1
// {
// id: 1,
// name: 'Laptop Pro',
// price: 1500
// }
const updatedProduct = await productRep.patchById(1, {
price: 1450,
});
console.log(updatedProduct);
// {
// id: 1,
// name: 'Laptop Pro',
// price: 1450
// }
Частично обновляет один или несколько документов, соответствующих
условиям where. Изменяются только переданные поля,
остальные остаются без изменений. Возвращает количество обновленных
документов. Если where не указан, обновляет все документы в
коллекции.
Сигнатура:
patch(
data: PartialWithoutId<FlatData, IdName>,
where?: WhereClause<FlatData>,
): Promise<number>;
Примеры
Обновление документов по условию.
// обновит все товары с ценой меньше 30
const updatedCount = await productRep.patch(
{inStock: false},
{price: {lt: 30}},
);
Обновление всех документов.
// добавит или обновит поле updatedAt для всех документов
const totalCount = await productRep.patch({
updatedAt: new Date(),
});
Находит все документы, соответствующие условиям фильтрации, и возвращает их в виде массива. Если фильтр не указан, возвращает все документы коллекции.
Сигнатура:
find(filter?: FilterClause<FlatData>): Promise<FlatData[]>;
Примеры
Поиск всех документов.
const allProducts = await productRep.find();
Поиск документов по условию where.
const cheapProducts = await productRep.find({
where: {price: {lt: 100}},
});
Поиск с сортировкой и ограничением выборки.
const latestProducts = await productRep.find({
order: 'createdAt DESC',
limit: 10,
});
Находит первый документ, соответствующий условиям фильтрации.
Возвращает undefined, если документы не найдены.
Сигнатура:
findOne(
filter?: FilterClause<FlatData>,
): Promise<FlatData | undefined>;
Примеры
Поиск одного документа по условию.
const expensiveProduct = await productRep.findOne({
where: {price: {gt: 1000}},
order: 'price DESC',
});
Обработка случая, когда документ не найден.
const product = await productRep.findOne({
where: {name: 'Non-existent Product'},
});
if (!product) {
console.log('Product not found.');
}
Находит один документ по его уникальному идентификатору. Если документ не найден, выбрасывается ошибка.
Сигнатура:
findById(
id: IdType,
filter?: ItemFilterClause<FlatData>,
): Promise<FlatData>;
Примеры
Поиск документа по id.
try {
const product = await productRep.findById(1);
console.log(product);
} catch (error) {
console.error('Product with id 1 is not found.');
}
Поиск документа с включением связанных данных.
const product = await productRep.findById(1, {
include: 'category',
});
Удаляет один или несколько документов, соответствующих условиям
where. Возвращает количество удаленных документов. Если
where не указан, удаляет все документы в коллекции.
Сигнатура:
delete(where?: WhereClause<FlatData>): Promise<number>;
Примеры
Удаление документов по условию.
const deletedCount = await productRep.delete({
inStock: false,
});
Удаление всех документов.
const totalCount = await productRep.delete();
Удаляет один документ по его уникальному идентификатору. Возвращает
true, если документ был найден и удален, в противном случае
false.
Сигнатура:
deleteById(id: IdType): Promise<boolean>;
Примеры
Удаление документа по id.
const wasDeleted = await productRep.deleteById(1);
if (wasDeleted) {
console.log('The document was deleted.');
} else {
console.log('No document found to delete.');
}
Проверяет существование документа с указанным идентификатором.
Возвращает true, если документ существует, иначе
false.
Сигнатура:
exists(id: IdType): Promise<boolean>;
Примеры
Проверка существования документа по id.
const productExists = await productRep.exists(1);
if (productExists) {
console.log('A document with id 1 exists.');
}
Подсчитывает количество документов, соответствующих условиям
where. Если where не указан, возвращает общее
количество документов в коллекции.
Сигнатура:
count(where?: WhereClause<FlatData>): Promise<number>;
Примеры
Подсчет документов по условию.
const cheapCount = await productRep.count({
price: {lt: 100},
});
Подсчет всех документов.
const totalCount = await productRep.count();
Некоторые методы репозитория принимают объект настроек влияющий на
возвращаемый результат. Максимально широкий набор таких настроек имеет
первый параметр метода find, где ожидается объект
содержащий набор опций указанных ниже.
where: object условия фильтрации по свойствам
документа;order: string|string[] сортировка по указанным
свойствам;limit: number ограничение количества документов;skip: number пропуск документов (пагинация);fields: string|string[] выбор необходимых свойств
модели;include: object включение связанных данных в
результат;Пример
// для запроса используется метод репозитория "find"
// с передачей объекта фильтрации первым аргументом
const news = await newsRepository.find({
where: {
title: {like: '%Moscow%'},
publishedAt: {gte: '2025-10-15T00:00:00.000Z'},
tags: {inq: ['world', 'politic']},
hidden: false,
},
order: 'publishedAt DESC',
limit: 12,
skip: 24,
fields: ['title', 'annotation', 'body'],
include: ['author', 'category'],
})
Параметр принимает объект с условиями выборки и поддерживает следующий набор операторов сравнения.
eq (строгое
равенство)neq (неравенство)gt (больше чем)lt (меньше чем)gte (больше или
равно)lte (меньше или
равно)inq (в списке)nin (не в списке)between (диапазон)exists (наличие
свойства)like (шаблон)nlike
(исключающий шаблон)ilike
(регистронезависимый шаблон)nilike
(регистронезависимый шаблон исключения)regexp
(регулярное выражение)Условия можно объединять логическими операторами:
Находит документы, у которых значение указанного свойства в точности
равно переданному значению. Это сокращенная запись для оператора
{eq: ...}.
// найдет все документы, где age равен 21
const res = await rep.find({
where: {
age: 21,
},
});
eq (строгое равенство)Находит документы, у которых значение свойства равно указанному.
// найдет все документы, где age равен 21
const res = await rep.find({
where: {
age: {eq: 21},
},
});
neq (неравенство)Находит документы, у которых значение свойства не равно указанному.
// найдет все документы, где age не равен 21
const res = await rep.find({
where: {
age: {neq: 21},
},
});
gt (больше чем)Находит документы, у которых значение свойства строго больше указанного.
// найдет документы, где age больше 30
const res = await rep.find({
where: {
age: {gt: 30},
},
});
lt (меньше чем)Находит документы, у которых значение свойства строго меньше указанного.
// найдет документы, где age меньше 30
const res = await rep.find({
where: {
age: {lt: 30},
},
});
gte (больше или равно)Находит документы, у которых значение свойства больше или равно указанному.
// найдет документы, где age больше или равен 30
const res = await rep.find({
where: {
age: {gte: 30},
},
});
lte (меньше или равно)Находит документы, у которых значение свойства меньше или равно указанному.
// найдет документы, где age меньше или равен 30
const res = await rep.find({
where: {
age: {lte: 30},
},
});
inq (в списке)Находит документы, у которых значение свойства совпадает с одним из значений в предоставленном массиве.
// найдет документы, где name - 'John' или 'Mary'
const res = await rep.find({
where: {
name: {inq: ['John', 'Mary']},
},
});
nin (не в списке)Находит документы, у которых значение свойства отсутствует в предоставленном массиве.
// найдет все документы, кроме тех, где name - 'John' или 'Mary'
const res = await rep.find({
where: {
name: {nin: ['John', 'Mary']},
},
});
between (диапазон)Находит документы, у которых значение свойства находится в указанном диапазоне (включая границы).
// найдет документы, где age находится в диапазоне от 20 до 30 включительно
const res = await rep.find({
where: {
age: {between: [20, 30]},
},
});
exists (наличие
свойства)Проверяет наличие или отсутствие свойства в документе. Не проверяет значение свойства.
true свойство должно существовать (даже если его
значение null);false свойство должно отсутствовать;// найдет документы, у которых есть свойство 'nickname'
const res1 = await rep.find({
where: {
nickname: {exists: true},
},
});
// найдет документы, у которых нет свойства 'nickname'
const res2 = await rep.find({
where: {
nickname: {exists: false},
},
});
like (шаблон)Выполняет сопоставление с шаблоном, с учетом регистра (см. подробнее).
// найдет {name: 'John Doe'}, но не {name: 'john doe'}
const res = await rep.find({
where: {
name: {like: 'John%'},
},
});
nlike (исключающий
шаблон)Находит документы, которые не соответствуют шаблону, с учетом регистра (см. подробнее).
// найдет все, кроме тех, что начинаются на 'John'
const res = await rep.find({
where: {
name: {nlike: 'John%'},
},
});
ilike
(регистронезависимый шаблон)Выполняет сопоставление с шаблоном без учета регистра (см. подробнее).
// найдет {name: 'John Doe'} и {name: 'john doe'}
const res = await rep.find({
where: {
name: {ilike: 'john%'},
},
});
nilike
(регистронезависимый шаблон исключения)Находит строки, которые не соответствуют шаблону, без учета регистра (см. подробнее).
// найдет все, кроме тех, что начинаются на 'John' или 'john'
const res = await rep.find({
where: {
name: {nilike: 'john%'},
},
});
regexp (регулярное
выражение)Находит документы, у которых значение строкового свойства
соответствует указанному регулярному выражению. Может быть передано в
виде строки или объекта RegExp.
// найдет документы, где name начинается с 'J'
const res1 = await rep.find({
where: {
name: {regexp: '^J'},
},
});
// найдет документы, где name начинается с 'J' или 'j' (регистронезависимо)
const res2 = await rep.find({
where: {
name: {regexp: '^j', flags: 'i'},
},
});
and (логическое И)Объединяет несколько условий в массив, требуя, чтобы каждое условие было выполнено.
// найдет документы, где surname равен 'Smith' И age равен 21
const res = await rep.find({
where: {
and: [
{surname: 'Smith'},
{age: 21}
],
},
});
or (логическое ИЛИ)Объединяет несколько условий в массив, требуя, чтобы хотя бы одно из них было выполнено.
// найдет документы, где name равен 'James' ИЛИ age больше 30
const res = await rep.find({
where: {
or: [
{name: 'James'},
{age: {gt: 30}}
],
},
});
Операторы like, nlike, ilike,
nilike предназначены для фильтрации строковых свойств на
основе сопоставления с шаблоном, подобно оператору LIKE в
SQL. Они позволяют находить значения, которые соответствуют определённой
структуре, используя специальные символы.
% соответствует любой
последовательности из нуля или более символов:
'А%' найдет все строки, начинающиеся на "А";'%а' найдет все строки, заканчивающиеся на "а";'%слово%' найдет все строки, содержащие "слово" в любом
месте;_ соответствует ровно одному любому
символу:
'к_т' найдет "кот", "кит", но не "крот" или "кт";'кот_' найдет "коты", "коту" и "кота", но не "кот" или
"котов";Если нужно найти сами символы % или _ как
часть строки, их необходимо экранировать с помощью обратного слэша
\:
'100\%' найдет строку "100%";'file\_name' найдет строку "file_name";'path\\to' найдет строку "path\to";Параметр упорядочивает выборку по указанным свойствам модели.
Обратное направление порядка можно задать постфиксом DESC в
названии свойства.
Примеры
Упорядочить по полю createdAt
const res = await rep.find({
order: 'createdAt',
});
Упорядочить по полю createdAt в обратном порядке.
const res = await rep.find({
order: 'createdAt DESC',
});
Упорядочить по нескольким свойствам в разных направлениях.
const res = await rep.find({
order: [
'title',
'price ASC',
'featured DESC',
],
});
i. Направление порядка ASC указывать
необязательно.
Параметр включает связанные документы в результат вызываемого метода. Названия включаемых связей должны быть определены в текущей модели. (см. Связи)
Примеры
Включение связи по названию.
const res = await rep.find({
include: 'city',
});
Включение вложенных связей.
const res = await rep.find({
include: {
city: 'country',
},
});
Включение нескольких связей массивом.
const res = await rep.find({
include: [
'city',
'address',
'employees'
],
});
Использование фильтрации включаемых документов.
const res = await rep.find({
include: {
relation: 'employees', // название связи
scope: { // фильтрация документов "employees"
where: {hidden: false}, // условия выборки
order: 'id', // порядок документов
limit: 10, // ограничение количества
skip: 5, // пропуск документов
fields: ['name', 'surname'], // только указанные поля
include: 'city', // включение связей для "employees"
},
},
});
Связи позволяют описывать отношения между моделями, что дает
возможность автоматически встраивать связанные данные с помощью опции
include в методах репозитория. Ниже приводится пример
автоматического разрешения связи при использовании метода
findById.
Роль (role)
┌────────────────────────┐
│ id: 3 <──────────────│────┐
│ name: 'Manager' │ │
└────────────────────────┘ │
│
Пользователь (user) │
┌────────────────────────┐ │
│ id: 1 │ │
│ name: 'John Doe' │ │
│ roleId: 3 ──────────│────┘
│ cityId: 24 ──────────│────┐
└────────────────────────┘ │
│
Город (city) │
┌────────────────────────┐ │
│ id: 24 <─────────────│────┘
│ name: 'Moscow' │
└────────────────────────┘
// запрос документа коллекции "users",
// включая связанные данные (role и city)
const user = await userRep.findById(1, {
include: ['role', 'city'],
});
console.log(user);
// {
// id: 1,
// name: 'John Doe',
// roleId: 3,
// role: {
// id: 3,
// name: 'Manager'
// },
// cityId: 24,
// city: {
// id: 24,
// name: 'Moscow'
// }
// }
Свойство relations в определении модели принимает
объект, ключи которого являются названиями связей, а значения их
параметрами. В дальнейшем название связи можно будет использовать в
опции include методах репозитория.
import {DataType} from '@e22m4u/js-repository';
import {RelationType} from '@e22m4u/js-repository';
dbs.defineModel({
name: 'user',
datasource: 'memory',
properties: {
name: DataType.STRING,
},
relations: {
// связь role -> параметры
role: {
type: RelationType.BELONGS_TO,
model: 'role',
},
// связь city -> параметры
city: {
type: RelationType.BELONGS_TO,
model: 'city',
},
},
});
Основные параметры
type: string тип связи (обязательно);model: string название целевой модели (обязательно для
некоторых типов);foreignKey: string свойство текущей модели для
идентификатора цели;i. Для типов Belongs To и References Many значение параметра
foreignKey можно опустить, так как генерируется
автоматически по названию связи.
Полиморфный режим
polymorphic: boolean|string объявление полиморфной
связи;discriminator: string свойство текущей модели для
названия цели;i. Полиморфный режим позволяет динамически определять целевую модель по ее названию, которое хранит документ в свойстве-дискриминаторе.
Типы связи
belongsTo - текущая модель ссылается на целевую по
идентификатору;hasOne - обратная сторона belongsTo по
принципу "один к одному";hasMany - обратная сторона belongsTo по
принципу "один ко многим";referencesMany - модель ссылается через массив
идентификаторов;Параметр type в определении связи принимает строку с
названием типа. Чтобы исключить опечатку, рекомендуется использовать
константы объекта RelationType указанные ниже.
RelationType.BELONGS_TORelationType.HAS_ONERelationType.HAS_MANYRelationType.REFERENCES_MANYПримеры
Объявление связи belongsTo.
Текущая (user) Целевая (role)
┌────────────────────┐ ┌────────────────────┐
│ id: 1 │ ┌───│─> id: 5 │
│ roleId: 5 ──────│───┤ │ ... │
│ ... │ │ └────────────────────┘
└────────────────────┘ │
┌────────────────────┐ │
│ id: 2 │ │
│ roleId: 5 ──────│───┘
│ ... │
└────────────────────┘
dbs.defineModel({
name: 'user',
relations: {
role: { // название связи
type: RelationType.BELONGS_TO, // текущая модель ссылается на целевую
model: 'role', // название целевой модели
foreignKey: 'roleId', // внешний ключ (необязательно)
// если "foreignKey" не указан, то свойство внешнего
// ключа формируется согласно названию связи
// с добавлением постфикса "Id"
},
},
});
Объявление связи hasOne.
Текущая (profile) Целевая (user)
┌───────────────────────┐ ┌────────────────────┐
│ id: 5 <────────────│───┐ │ id: 1 │
│ ... │ └───│── profileId: 5 │
└───────────────────────┘ │ ... │
└────────────────────┘
dbs.defineModel({
name: 'profile',
relations: {
users: { // название связи
type: RelationType.HAS_ONE, // целевая модель ссылается на текущую
model: 'user', // название целевой модели
foreignKey: 'profileId', // внешний ключ из целевой модели на текущую
},
},
});
Объявление связи hasMany.
Текущая (role) Целевая (user)
┌────────────────────┐ ┌────────────────────┐
│ id: 5 <─────────│───┐ │ id: 1 │
│ ... │ ├───│── roleId: 5 │
└────────────────────┘ │ │ ... │
│ └────────────────────┘
│ ┌────────────────────┐
│ │ id: 2 │
└───│── roleId: 5 │
│ ... │
└────────────────────┘
dbs.defineModel({
name: 'role',
relations: {
users: { // название связи
type: RelationType.HAS_MANY, // целевая модель ссылается на текущую
model: 'user', // название целевой модели
foreignKey: 'roleId', // внешний ключ из целевой модели на текущую
},
},
});
Объявление связи referencesMany.
Текущая (article) Целевая (category)
┌─────────────────────────┐ ┌────────────────────────┐
│ id: 1 │ ┌───│─> id: 5 │
│ categoryIds: [5, 6] ──│───│ │ ... │
│ ... │ │ └────────────────────────┘
└─────────────────────────┘ │ ┌────────────────────────┐
└───│─> id: 6 │
│ ... │
└────────────────────────┘
dbs.defineModel({
name: 'article',
relations: {
categories: { // название связи
type: RelationType.REFERENCES_MANY, // связь через массив идентификаторов
model: 'category', // название целевой модели
foreignKey: 'categoryIds', // внешний ключ (необязательно)
// если "foreignKey" не указан, то свойство внешнего
// ключа формируется согласно названию связи
// с добавлением постфикса "Ids"
},
},
});
Полиморфная версия belongsTo.
dbs.defineModel({
name: 'file',
relations: {
reference: { // название связи
type: RelationType.BELONGS_TO, // текущая модель ссылается на целевую
// полиморфный режим позволяет хранить название целевой модели
// в свойстве-дискриминаторе, которое формируется согласно
// названию связи с постфиксом "Type", и в данном случае
// название целевой модели хранит "referenceType",
// а идентификатор документа "referenceId"
polymorphic: true,
},
},
});
Полиморфная версия belongsTo с указанием свойств.
dbs.defineModel({
name: 'file',
relations: {
reference: { // название связи
type: RelationType.BELONGS_TO, // текущая модель ссылается на целевую
polymorphic: true, // название целевой модели хранит дискриминатор
foreignKey: 'referenceId', // свойство для идентификатора цели
discriminator: 'referenceType', // свойство для названия целевой модели
},
},
});
Полиморфная версия hasMany с указанием названия связи
целевой модели.
dbs.defineModel({
name: 'letter',
relations: {
attachments: { // название связи
type: RelationType.HAS_MANY, // целевая модель ссылается на текущую
model: 'file', // название целевой модели
polymorphic: 'reference', // название полиморфной связи целевой модели
},
},
});
Полиморфная версия hasMany с указанием свойств целевой
модели.
dbs.defineModel({
name: 'letter',
relations: {
attachments: { // название связи
type: RelationType.HAS_MANY, // целевая модель ссылается на текущую
model: 'file', // название целевой модели
polymorphic: true, // название текущей модели находится в дискриминаторе
foreignKey: 'referenceId', // свойство целевой модели для идентификатора
discriminator: 'referenceType', // свойство целевой модели для названия текущей
},
},
});
Метод getRepository экземпляра
DatabaseSchema проверяет наличие существующего репозитория
для указанной модели и возвращает его. В противном случае создается
новый экземпляр, который будет сохранен для последующих обращений к
методу.
import {Repository} from '@e22m4u/js-repository';
import {DatabaseSchema} from '@e22m4u/js-repository';
// const dbs = new DatabaseSchema();
// dbs.defineDatasource ...
// dbs.defineModel ...
const rep1 = dbs.getRepository('model');
const rep2 = dbs.getRepository('model');
console.log(rep1 === rep2); // true
Подмена стандартного конструктора репозитория выполняется методом
setRepositoryCtor сервиса RepositoryRegistry,
который находится в сервис-контейнере экземпляра
DatabaseSchema. После чего все новые репозитории будут
создаваться указанным конструктором вместо стандартного.
import {Repository} from '@e22m4u/js-repository';
import {DatabaseSchema} from '@e22m4u/js-repository';
import {RepositoryRegistry} from '@e22m4u/js-repository';
class MyRepository extends Repository {
/*...*/
}
// const dbs = new DatabaseSchema();
// dbs.defineDatasource ...
// dbs.defineModel ...
dbs.getService(RepositoryRegistry).setRepositoryCtor(MyRepository);
const rep = dbs.getRepository('model');
console.log(rep instanceof MyRepository); // true
i. Так как экземпляры репозитория кэшируется, то замену
конструктора следует выполнять до обращения к методу
getRepository.
Получение типизированного репозитория с указанием интерфейса модели.
import {DataType} from '@e22m4u/js-repository';
import {RelationType} from '@e22m4u/js-repository';
import {DatabaseSchema} from '@e22m4u/js-repository';
// const dbs = new DatabaseSchema();
// dbs.defineDatasource ...
// dbs.defineModel ...
// определение модели "city"
dbs.defineModel({
name: 'city',
datasource: 'myDatasource',
properties: {
name: DataType.STRING,
timeZone: DataType.STRING,
},
relations: {
country: {
type: RelationType.BELONGS_TO,
model: 'country',
},
},
});
// определение интерфейса "city"
interface City {
id: number;
name?: string;
timeZone?: string;
countryId?: number;
country?: Country;
}
// получаем репозиторий по названию модели
// указывая ее тип и тип идентификатора
const cityRep = dbs.getRepository<City, number>('city');
npm run test
MIT