@e22m4u/js-repository 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +982 -93
- package/dist/cjs/index.cjs +21 -21
- package/package.json +12 -12
- package/src/definition/model/model-data-transformer.js +2 -2
- package/src/definition/model/model-data-validator.js +3 -3
- package/src/definition/model/properties/property-uniqueness-validator.js +2 -2
- package/src/filter/filter-clause.d.ts +3 -4
- package/src/filter/where-clause-tool.js +2 -2
- package/src/utils/index.d.ts +1 -1
- package/src/utils/index.js +1 -1
- package/src/utils/is-plain-object.d.ts +6 -0
- package/src/utils/{is-pure-object.js → is-plain-object.js} +2 -2
- package/src/utils/is-plain-object.spec.js +25 -0
- package/src/utils/is-pure-object.d.ts +0 -6
- package/src/utils/is-pure-object.spec.js +0 -25
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
## @e22m4u/js-repository
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
Реализация паттерна «Репозиторий» для работы с базами данных.
|
|
7
|
+
|
|
8
|
+
## Содержание
|
|
4
9
|
|
|
5
10
|
- [Установка](#установка)
|
|
6
11
|
- [Импорт](#импорт)
|
|
@@ -21,8 +26,27 @@
|
|
|
21
26
|
- [Пустые значения](#пустые-значения)
|
|
22
27
|
- [Переопределение пустых значений](#переопределение-пустых-значений)
|
|
23
28
|
- [Репозиторий](#репозиторий)
|
|
29
|
+
- [create](#repositorycreate)
|
|
30
|
+
- [replaceById](#repositoryreplacebyid)
|
|
31
|
+
- [replaceOrCreate](#repositoryreplaceorcreate)
|
|
32
|
+
- [patchById](#repositorypatchbyid)
|
|
33
|
+
- [patch](#repositorypatch)
|
|
34
|
+
- [find](#repositoryfind)
|
|
35
|
+
- [findOne](#repositoryfindone)
|
|
36
|
+
- [findById](#repositoryfindbyid)
|
|
37
|
+
- [delete](#repositorydelete)
|
|
38
|
+
- [deleteById](#repositorydeletebyid)
|
|
39
|
+
- [exists](#repositoryexists)
|
|
40
|
+
- [count](#repositorycount)
|
|
24
41
|
- [Фильтрация](#фильтрация)
|
|
25
42
|
- [Связи](#связи)
|
|
43
|
+
- [Belongs To](#belongs-to)
|
|
44
|
+
- [Has One](#has-one)
|
|
45
|
+
- [Has Many](#has-many)
|
|
46
|
+
- [References Many](#references-many)
|
|
47
|
+
- [Belongs To (полиморфная)](#belongs-to-полиморфная-версия)
|
|
48
|
+
- [Has One (полиморфная)](#has-one-полиморфная-версия)
|
|
49
|
+
- [Has Many (полиморфная)](#has-many-полиморфная-версия)
|
|
26
50
|
- [Расширение](#расширение)
|
|
27
51
|
- [TypeScript](#typescript)
|
|
28
52
|
- [Тесты](#тесты)
|
|
@@ -36,10 +60,10 @@ npm install @e22m4u/js-repository
|
|
|
36
60
|
|
|
37
61
|
Опционально устанавливается нужный адаптер.
|
|
38
62
|
|
|
39
|
-
| адаптер | описание
|
|
40
|
-
|
|
41
|
-
| `memory` | Виртуальная база в памяти процесса
|
|
42
|
-
| `mongodb` | MongoDB -
|
|
63
|
+
| адаптер | описание | установка |
|
|
64
|
+
|-----------|-------------------------------------------------|----------------------------------------------------------------------------|
|
|
65
|
+
| `memory` | Виртуальная база в памяти процесса | *встроенный* |
|
|
66
|
+
| `mongodb` | MongoDB - документо-ориентированная база данных | [npm](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter) |
|
|
43
67
|
|
|
44
68
|
## Импорт
|
|
45
69
|
|
|
@@ -68,7 +92,7 @@ const {DatabaseSchema} = require('@e22m4u/js-repository');
|
|
|
68
92
|
«один к одному», «один ко многим» и другие типы отношений между моделями.
|
|
69
93
|
|
|
70
94
|
Непосредственно чтение и запись данных производится с помощью *репозитория*,
|
|
71
|
-
который
|
|
95
|
+
который есть у каждой модели с объявленным *источником данных*. Репозиторий
|
|
72
96
|
может фильтровать запрашиваемые документы, выполнять валидацию свойств
|
|
73
97
|
согласно определению модели, и встраивать связанные данные в результат
|
|
74
98
|
выборки.
|
|
@@ -111,7 +135,18 @@ flowchart TD
|
|
|
111
135
|
|
|
112
136
|
## Пример
|
|
113
137
|
|
|
114
|
-
|
|
138
|
+
Пример демонстрирует создание экземпляра схемы, объявление источника данных
|
|
139
|
+
и модели `country`. После чего, с помощью репозитория данной модели, в коллекцию
|
|
140
|
+
добавляется новый документ (страна), который выводится в консоль.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
Страна (country)
|
|
144
|
+
┌─────────────────────────┐
|
|
145
|
+
│ id: 1 │
|
|
146
|
+
│ name: "Russia" │
|
|
147
|
+
│ population: 143400000 │
|
|
148
|
+
└─────────────────────────┘
|
|
149
|
+
```
|
|
115
150
|
|
|
116
151
|
```js
|
|
117
152
|
import {DataType} from '@e22m4u/js-repository';
|
|
@@ -120,18 +155,18 @@ import {DatabaseSchema} from '@e22m4u/js-repository';
|
|
|
120
155
|
// создание экземпляра DatabaseSchema
|
|
121
156
|
const dbs = new DatabaseSchema();
|
|
122
157
|
|
|
123
|
-
// объявление источника "
|
|
158
|
+
// объявление источника "myDb"
|
|
124
159
|
dbs.defineDatasource({
|
|
125
|
-
name: '
|
|
160
|
+
name: 'myDb', // название нового источника
|
|
126
161
|
adapter: 'memory', // выбранный адаптер
|
|
127
162
|
});
|
|
128
163
|
|
|
129
164
|
// объявление модели "country"
|
|
130
165
|
dbs.defineModel({
|
|
131
|
-
name: 'country',
|
|
132
|
-
datasource: '
|
|
133
|
-
properties: {
|
|
134
|
-
name: DataType.STRING,
|
|
166
|
+
name: 'country', // название новой модели
|
|
167
|
+
datasource: 'myDb', // выбранный источник
|
|
168
|
+
properties: { // свойства модели
|
|
169
|
+
name: DataType.STRING, // тип "string"
|
|
135
170
|
population: DataType.NUMBER, // тип "number"
|
|
136
171
|
},
|
|
137
172
|
})
|
|
@@ -154,6 +189,81 @@ console.log(country);
|
|
|
154
189
|
// }
|
|
155
190
|
```
|
|
156
191
|
|
|
192
|
+
В следующем блоке определяется модель `city` со связью `belongsTo` к модели
|
|
193
|
+
`country` из примера выше. Затем создается новый документ города, связанный
|
|
194
|
+
с ранее созданной страной. После создания нового документа, выполняется запрос
|
|
195
|
+
на извлечение данного города с включением связанной страны.
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
Страна (country) Город (city)
|
|
199
|
+
┌─────────────────────────┐ ┌─────────────────────────┐
|
|
200
|
+
│ id: 1 <───────────────│───┐ │ id: 1 │
|
|
201
|
+
│ name: "Russia" │ │ │ name: "Moscow" │
|
|
202
|
+
│ population: 143400000 │ └───│─ countryId: 1 │
|
|
203
|
+
└─────────────────────────┘ └─────────────────────────┘
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
// объявление модели "city" со связью к "country"
|
|
208
|
+
dbs.defineModel({
|
|
209
|
+
name: 'city',
|
|
210
|
+
datasource: 'myDb',
|
|
211
|
+
properties: {
|
|
212
|
+
name: DataType.STRING,
|
|
213
|
+
countryId: DataType.NUMBER,
|
|
214
|
+
// внешний ключ "countryId" указывать не обязательно,
|
|
215
|
+
// но для проверки типа значения перед записью в базу
|
|
216
|
+
// рекомендуется, так как адаптер "memory" по умолчанию
|
|
217
|
+
// создает числовые идентификаторы
|
|
218
|
+
},
|
|
219
|
+
relations: {
|
|
220
|
+
// определение связи "country" позволит автоматически включать
|
|
221
|
+
// связанные документы с помощью опции "include" при запросах
|
|
222
|
+
// из данной коллекции через методы репозитория
|
|
223
|
+
country: {
|
|
224
|
+
type: RelationType.BELONGS_TO, // тип связи: принадлежит к...
|
|
225
|
+
model: 'country', // название целевой модели
|
|
226
|
+
foreignKey: 'countryId', // поле с внешним ключом (не обязательно)
|
|
227
|
+
// если внешний ключ соответствует `relationName` + `Id`,
|
|
228
|
+
// то указывать опцию `foreignKey` не обязательно
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// получение репозитория для модели "city"
|
|
234
|
+
const cityRep = dbs.getRepository('city');
|
|
235
|
+
|
|
236
|
+
// создание нового города и его привязка к стране через country.id
|
|
237
|
+
const city = await cityRep.create({
|
|
238
|
+
name: 'Moscow',
|
|
239
|
+
countryId: country.id, // использование id созданной ранее страны
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
console.log(city);
|
|
243
|
+
// {
|
|
244
|
+
// id: 1,
|
|
245
|
+
// name: 'Moscow',
|
|
246
|
+
// countryId: 1,
|
|
247
|
+
// }
|
|
248
|
+
|
|
249
|
+
// извлечение города по идентификатору с включением связанной страны
|
|
250
|
+
const cityWithCountry = await cityRep.findById(city.id, {
|
|
251
|
+
include: 'country',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
console.log(cityWithCountry);
|
|
255
|
+
// {
|
|
256
|
+
// id: 1,
|
|
257
|
+
// name: 'Moscow',
|
|
258
|
+
// countryId: 1,
|
|
259
|
+
// country: {
|
|
260
|
+
// id: 1,
|
|
261
|
+
// name: 'Russia',
|
|
262
|
+
// population: 143400000
|
|
263
|
+
// }
|
|
264
|
+
// }
|
|
265
|
+
```
|
|
266
|
+
|
|
157
267
|
## Схема
|
|
158
268
|
|
|
159
269
|
Экземпляр класса `DatabaseSchema` хранит определения источников данных и моделей.
|
|
@@ -178,7 +288,7 @@ const dbs = new DatabaseSchema();
|
|
|
178
288
|
|
|
179
289
|
```js
|
|
180
290
|
dbs.defineDatasource({
|
|
181
|
-
name: '
|
|
291
|
+
name: 'myDb', // название нового источника
|
|
182
292
|
adapter: 'memory', // выбранный адаптер
|
|
183
293
|
});
|
|
184
294
|
```
|
|
@@ -188,7 +298,7 @@ dbs.defineDatasource({
|
|
|
188
298
|
```js
|
|
189
299
|
dbs.defineModel({
|
|
190
300
|
name: 'product', // название новой модели
|
|
191
|
-
datasource: '
|
|
301
|
+
datasource: 'myDb', // выбранный источник
|
|
192
302
|
properties: { // свойства модели
|
|
193
303
|
name: DataType.STRING,
|
|
194
304
|
weight: DataType.NUMBER,
|
|
@@ -220,16 +330,16 @@ const productRep = dbs.getRepository('product');
|
|
|
220
330
|
|
|
221
331
|
```js
|
|
222
332
|
dbs.defineDatasource({
|
|
223
|
-
name: '
|
|
333
|
+
name: 'myDb', // название нового источника
|
|
224
334
|
adapter: 'memory', // выбранный адаптер
|
|
225
335
|
});
|
|
226
336
|
```
|
|
227
337
|
|
|
228
|
-
Передача дополнительных параметров
|
|
338
|
+
Передача дополнительных параметров на примере MongoDB адаптера *([установка](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter))*.
|
|
229
339
|
|
|
230
340
|
```js
|
|
231
341
|
dbs.defineDatasource({
|
|
232
|
-
name: '
|
|
342
|
+
name: 'myDb',
|
|
233
343
|
adapter: 'mongodb',
|
|
234
344
|
// параметры адаптера "mongodb"
|
|
235
345
|
host: '127.0.0.1',
|
|
@@ -298,8 +408,8 @@ dbs.defineModel({
|
|
|
298
408
|
|
|
299
409
|
Если значением параметра `unique` является `true` или `'strict'`, то выполняется
|
|
300
410
|
строгая проверка на уникальность. В этом режиме [пустые значения](#Пустые-значения)
|
|
301
|
-
так же подлежат проверке, где `null` и `undefined`
|
|
302
|
-
|
|
411
|
+
так же подлежат проверке, где `null` и `undefined` также считаются значениями,
|
|
412
|
+
которые должны быть уникальными.
|
|
303
413
|
|
|
304
414
|
Режим `'sparse'` проверяет только значения с полезной нагрузкой, исключая
|
|
305
415
|
[пустые значения](#Пустые-значения), список которых отличается в зависимости
|
|
@@ -395,8 +505,8 @@ dbs.defineModel({
|
|
|
395
505
|
|
|
396
506
|
Валидаторы указанные ниже находятся в разработке:
|
|
397
507
|
|
|
398
|
-
- `isLowerCase` проверка регистра (только
|
|
399
|
-
- `isUpperCase` проверка регистра (только
|
|
508
|
+
- `isLowerCase` проверка регистра (только строчные буквы);
|
|
509
|
+
- `isUpperCase` проверка регистра (только прописные буквы);
|
|
400
510
|
- `isEmail` проверка формата электронного адреса;
|
|
401
511
|
|
|
402
512
|
**Примеры**
|
|
@@ -493,7 +603,7 @@ import {Errorf} from '@e22m4u/js-format';
|
|
|
493
603
|
import {PropertyValidatorRegistry} from '@e22m4u/js-repository';
|
|
494
604
|
|
|
495
605
|
// получение экземпляра сервиса
|
|
496
|
-
const pvr = dbs.
|
|
606
|
+
const pvr = dbs.getService(PropertyValidatorRegistry);
|
|
497
607
|
|
|
498
608
|
// регулярные выражения для разных версий UUID
|
|
499
609
|
const uuidRegex = {
|
|
@@ -762,7 +872,7 @@ dbs.defineModel({
|
|
|
762
872
|
import {PropertyTransformerRegistry} from '@e22m4u/js-repository';
|
|
763
873
|
|
|
764
874
|
// получение экземпляра сервиса
|
|
765
|
-
const ptr = dbs.
|
|
875
|
+
const ptr = dbs.getService(PropertyTransformerRegistry);
|
|
766
876
|
|
|
767
877
|
// регистрация глобального трансформера "stripTags"
|
|
768
878
|
ptr.addTransformer('stripTags', (value, options, context) => {
|
|
@@ -891,13 +1001,13 @@ dbs.defineModel({
|
|
|
891
1001
|
|
|
892
1002
|
## Пустые значения
|
|
893
1003
|
|
|
894
|
-
Разные типы свойств имеют свои наборы пустых значений. Эти наборы
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1004
|
+
Разные типы свойств имеют свои наборы пустых значений. Эти наборы используются
|
|
1005
|
+
для определения наличия полезной нагрузки в значении свойства. Например,
|
|
1006
|
+
параметр `default` в определении свойства устанавливает значение по умолчанию,
|
|
1007
|
+
только если входящее значение является пустым. Параметр `required` исключает
|
|
1008
|
+
пустые значения выбрасывая ошибку. А параметр `unique` в режиме `sparse`
|
|
1009
|
+
наоборот допускает дублирование пустых значений уникального свойства,
|
|
1010
|
+
поскольку они не участвуют в проверке.
|
|
901
1011
|
|
|
902
1012
|
| тип | пустые значения |
|
|
903
1013
|
|-------------|---------------------------|
|
|
@@ -966,23 +1076,23 @@ emptyValuesService.setEmptyValuesOf(DataType.NUMBER, [undefined, null]);
|
|
|
966
1076
|
|
|
967
1077
|
## Репозиторий
|
|
968
1078
|
|
|
969
|
-
|
|
970
|
-
|
|
1079
|
+
Репозиторий выполняет операции чтения и записи данных определенной модели.
|
|
1080
|
+
Он выступает в роли посредника между бизнес-логикой приложения и базой данных.
|
|
971
1081
|
|
|
972
1082
|
**Методы**
|
|
973
1083
|
|
|
974
|
-
- `create(data, filter = undefined)`
|
|
975
|
-
- `replaceById(id, data, filter = undefined)` заменить
|
|
976
|
-
- `replaceOrCreate(data, filter = undefined)` заменить или создать
|
|
977
|
-
- `patchById(id, data, filter = undefined)`
|
|
978
|
-
- `patch(data, where = undefined)` обновить все документы или по
|
|
979
|
-
- `find(filter = undefined)` найти все документы или по
|
|
980
|
-
- `findOne(filter = undefined)` найти первый документ или по
|
|
981
|
-
- `findById(id, filter = undefined)` найти документ по
|
|
982
|
-
- `delete(where = undefined)` удалить все документы или по
|
|
983
|
-
- `deleteById(id)` удалить документ по
|
|
984
|
-
- `exists(id)` проверить существование по
|
|
985
|
-
- `count(where = undefined)` подсчет всех документов или по
|
|
1084
|
+
- [`create(data, filter = undefined)`](#repositorycreate) создать новый документ;
|
|
1085
|
+
- [`replaceById(id, data, filter = undefined)`](#repositoryreplacebyid) заменить документ полностью;
|
|
1086
|
+
- [`replaceOrCreate(data, filter = undefined)`](#repositoryreplaceorcreate) заменить или создать новый;
|
|
1087
|
+
- [`patchById(id, data, filter = undefined)`](#repositorypatchbyid) обновить документ частично;
|
|
1088
|
+
- [`patch(data, where = undefined)`](#repositorypatch) обновить все документы или по условию;
|
|
1089
|
+
- [`find(filter = undefined)`](#repositoryfind) найти все документы или по условию;
|
|
1090
|
+
- [`findOne(filter = undefined)`](#repositoryfindone) найти первый документ или по условию;
|
|
1091
|
+
- [`findById(id, filter = undefined)`](#repositoryfindbyid) найти документ по идентификатору;
|
|
1092
|
+
- [`delete(where = undefined)`](#repositorydelete) удалить все документы или по условию;
|
|
1093
|
+
- [`deleteById(id)`](#repositorydeletebyid) удалить документ по идентификатору;
|
|
1094
|
+
- [`exists(id)`](#repositoryexists) проверить существование по идентификатору;
|
|
1095
|
+
- [`count(where = undefined)`](#repositorycount) подсчет всех документов или по условию;
|
|
986
1096
|
|
|
987
1097
|
**Аргументы**
|
|
988
1098
|
|
|
@@ -991,49 +1101,472 @@ emptyValuesService.setEmptyValuesOf(DataType.NUMBER, [undefined, null]);
|
|
|
991
1101
|
- `where: object` условия фильтрации (см. [Фильтрация](#Фильтрация))
|
|
992
1102
|
- `filter: object` параметры выборки (см. [Фильтрация](#Фильтрация))
|
|
993
1103
|
|
|
1104
|
+
**Получение репозитория**
|
|
1105
|
+
|
|
1106
|
+
Получить репозиторий можно с помощью метода `getRepository()` экземпляра
|
|
1107
|
+
`DatabaseSchema`. В качестве аргумента метод принимает название модели.
|
|
1108
|
+
Обязательным условием является наличие у модели определенного [источника данных](#источник-данных) (`datasource`), так как репозиторий напрямую взаимодействует
|
|
1109
|
+
с базой данных через указанный в источнике адаптер.
|
|
1110
|
+
|
|
1111
|
+
```js
|
|
1112
|
+
// объявление источника
|
|
1113
|
+
dbs.defineDatasource({
|
|
1114
|
+
name: 'myDatasource',
|
|
1115
|
+
adapter: 'memory', // адаптер
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
// объявление модели
|
|
1119
|
+
dbs.defineModel({
|
|
1120
|
+
name: 'myModel',
|
|
1121
|
+
datasource: 'myDatasource',
|
|
1122
|
+
// properties: { ... },
|
|
1123
|
+
// relations: { ... }
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
// получение репозитория модели
|
|
1127
|
+
const modelRep = dbs.getRepository('myModel');
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
При первом вызове `getRepository('myModel')` будет создан и сохранен новый
|
|
1131
|
+
экземпляр репозитория. Все последующие вызовы с тем же названием модели будут
|
|
1132
|
+
возвращать уже существующий экземпляр.
|
|
1133
|
+
|
|
1134
|
+
### repository.create
|
|
1135
|
+
|
|
1136
|
+
Создает новый документ в коллекции на основе переданных данных. Возвращает
|
|
1137
|
+
созданный документ с присвоенным идентификатором.
|
|
1138
|
+
|
|
1139
|
+
Сигнатура:
|
|
1140
|
+
|
|
1141
|
+
```ts
|
|
1142
|
+
create(
|
|
1143
|
+
data: WithOptionalId<FlatData, IdName>,
|
|
1144
|
+
filter?: ItemFilterClause<FlatData>,
|
|
1145
|
+
): Promise<FlatData>;
|
|
1146
|
+
```
|
|
1147
|
+
|
|
994
1148
|
**Примеры**
|
|
995
1149
|
|
|
996
|
-
|
|
1150
|
+
Создание нового документа.
|
|
997
1151
|
|
|
998
1152
|
```js
|
|
999
|
-
const
|
|
1153
|
+
const newProduct = await productRep.create({
|
|
1154
|
+
name: 'Laptop',
|
|
1155
|
+
price: 1200,
|
|
1156
|
+
});
|
|
1157
|
+
console.log(newProduct);
|
|
1158
|
+
// {
|
|
1159
|
+
// id: 1,
|
|
1160
|
+
// name: 'Laptop',
|
|
1161
|
+
// price: 1200,
|
|
1162
|
+
// }
|
|
1000
1163
|
```
|
|
1001
1164
|
|
|
1002
|
-
|
|
1165
|
+
Создание документа с возвратом определенных полей.
|
|
1003
1166
|
|
|
1004
1167
|
```js
|
|
1005
|
-
const
|
|
1006
|
-
name: '
|
|
1007
|
-
|
|
1168
|
+
const product = await productRep.create(
|
|
1169
|
+
{name: 'Mouse', price: 25},
|
|
1170
|
+
{fields: ['id', 'name']},
|
|
1171
|
+
);
|
|
1172
|
+
console.log(product);
|
|
1173
|
+
// {
|
|
1174
|
+
// id: 2,
|
|
1175
|
+
// name: 'Mouse',
|
|
1176
|
+
// }
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
Создание документа с включением связанных данных в результат.
|
|
1180
|
+
|
|
1181
|
+
```js
|
|
1182
|
+
// предполагается, что модель Product имеет связь "category"
|
|
1183
|
+
// (опция "include" влияет только на возвращаемый результат)
|
|
1184
|
+
const product = await productRep.create(
|
|
1185
|
+
{name: 'Keyboard', price: 75, categoryId: 10},
|
|
1186
|
+
{include: 'category'},
|
|
1187
|
+
);
|
|
1188
|
+
console.log(product);
|
|
1189
|
+
// {
|
|
1190
|
+
// id: 3,
|
|
1191
|
+
// name: 'Keyboard',
|
|
1192
|
+
// price: 75,
|
|
1193
|
+
// categoryId: 10,
|
|
1194
|
+
// category: {id: 10, name: 'Electronics'}
|
|
1195
|
+
// }
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1198
|
+
### repository.replaceById
|
|
1199
|
+
|
|
1200
|
+
Полностью заменяет существующий документ по его идентификатору. Все предыдущие
|
|
1201
|
+
данные документа, кроме идентификатора, удаляются. Поля, которые не были
|
|
1202
|
+
переданы в `data`, будут отсутствовать в итоговом документе (если для них
|
|
1203
|
+
не задано значение по умолчанию).
|
|
1204
|
+
|
|
1205
|
+
Сигнатура:
|
|
1206
|
+
|
|
1207
|
+
```ts
|
|
1208
|
+
replaceById(
|
|
1209
|
+
id: IdType,
|
|
1210
|
+
data: WithoutId<FlatData, IdName>,
|
|
1211
|
+
filter?: ItemFilterClause<FlatData>,
|
|
1212
|
+
): Promise<FlatData>;
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
**Примеры**
|
|
1216
|
+
|
|
1217
|
+
Замена документа по идентификатору.
|
|
1218
|
+
|
|
1219
|
+
```js
|
|
1220
|
+
// исходный документ
|
|
1221
|
+
// {
|
|
1222
|
+
// id: 1,
|
|
1223
|
+
// name: 'Laptop',
|
|
1224
|
+
// price: 1200,
|
|
1225
|
+
// inStock: true
|
|
1226
|
+
// }
|
|
1227
|
+
|
|
1228
|
+
const updatedProduct = await productRep.replaceById(1, {
|
|
1229
|
+
name: 'Laptop Pro',
|
|
1230
|
+
price: 1500,
|
|
1008
1231
|
});
|
|
1232
|
+
console.log(updatedProduct);
|
|
1233
|
+
// {
|
|
1234
|
+
// id: 1,
|
|
1235
|
+
// name: 'Laptop Pro',
|
|
1236
|
+
// price: 1500
|
|
1237
|
+
// }
|
|
1238
|
+
// свойство "inStock" удалено
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
### repository.replaceOrCreate
|
|
1242
|
+
|
|
1243
|
+
Заменяет существующий документ, если в переданных данных присутствует
|
|
1244
|
+
идентификатор, который уже существует в коллекции. В противном случае,
|
|
1245
|
+
если идентификатор не указан или не найден, создает новый документ.
|
|
1246
|
+
|
|
1247
|
+
Сигнатура:
|
|
1248
|
+
|
|
1249
|
+
```ts
|
|
1250
|
+
replaceOrCreate(
|
|
1251
|
+
data: WithOptionalId<FlatData, IdName>,
|
|
1252
|
+
filter?: ItemFilterClause<FlatData>,
|
|
1253
|
+
): Promise<FlatData>;
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
**Примеры**
|
|
1009
1257
|
|
|
1010
|
-
|
|
1258
|
+
Создание нового документа, если `id: 3` не существует.
|
|
1259
|
+
|
|
1260
|
+
```js
|
|
1261
|
+
const product = await productRep.replaceOrCreate({
|
|
1262
|
+
id: 3,
|
|
1263
|
+
name: 'Keyboard',
|
|
1264
|
+
price: 75,
|
|
1265
|
+
});
|
|
1266
|
+
console.log(product);
|
|
1011
1267
|
// {
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
1014
|
-
//
|
|
1268
|
+
// id: 3,
|
|
1269
|
+
// name: 'Keyboard',
|
|
1270
|
+
// price: 75,
|
|
1015
1271
|
// }
|
|
1016
1272
|
```
|
|
1017
1273
|
|
|
1018
|
-
|
|
1274
|
+
Замена существующего документа с `id: 1`.
|
|
1019
1275
|
|
|
1020
1276
|
```js
|
|
1021
|
-
const
|
|
1277
|
+
const updatedProduct = await productRep.replaceOrCreate({
|
|
1278
|
+
id: 1,
|
|
1279
|
+
name: 'Laptop Pro',
|
|
1280
|
+
price: 1500,
|
|
1281
|
+
});
|
|
1282
|
+
console.log(updatedProduct);
|
|
1283
|
+
// {
|
|
1284
|
+
// id: 1,
|
|
1285
|
+
// name: 'Laptop Pro',
|
|
1286
|
+
// price: 1500,
|
|
1287
|
+
// }
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
### repository.patchById
|
|
1291
|
+
|
|
1292
|
+
Частично обновляет существующий документ по его идентификатору, изменяя
|
|
1293
|
+
только переданные поля. Остальные поля документа остаются без изменений.
|
|
1022
1294
|
|
|
1023
|
-
|
|
1295
|
+
Сигнатура:
|
|
1296
|
+
|
|
1297
|
+
```ts
|
|
1298
|
+
patchById(
|
|
1299
|
+
id: IdType,
|
|
1300
|
+
data: PartialWithoutId<FlatData, IdName>,
|
|
1301
|
+
filter?: ItemFilterClause<FlatData>,
|
|
1302
|
+
): Promise<FlatData>;
|
|
1303
|
+
```
|
|
1304
|
+
|
|
1305
|
+
**Примеры**
|
|
1306
|
+
|
|
1307
|
+
Частичное обновление документа по идентификатору.
|
|
1308
|
+
|
|
1309
|
+
```js
|
|
1310
|
+
// исходный документ с id: 1
|
|
1311
|
+
// {
|
|
1312
|
+
// id: 1,
|
|
1313
|
+
// name: 'Laptop Pro',
|
|
1314
|
+
// price: 1500
|
|
1315
|
+
// }
|
|
1316
|
+
|
|
1317
|
+
const updatedProduct = await productRep.patchById(1, {
|
|
1318
|
+
price: 1450,
|
|
1319
|
+
});
|
|
1320
|
+
console.log(updatedProduct);
|
|
1024
1321
|
// {
|
|
1025
|
-
//
|
|
1026
|
-
//
|
|
1027
|
-
//
|
|
1322
|
+
// id: 1,
|
|
1323
|
+
// name: 'Laptop Pro',
|
|
1324
|
+
// price: 1450
|
|
1028
1325
|
// }
|
|
1029
1326
|
```
|
|
1030
1327
|
|
|
1031
|
-
|
|
1328
|
+
### repository.patch
|
|
1329
|
+
|
|
1330
|
+
Частично обновляет один или несколько документов, соответствующих условиям
|
|
1331
|
+
`where`. Изменяются только переданные поля, остальные остаются без изменений.
|
|
1332
|
+
Возвращает количество обновленных документов. Если `where` не указан,
|
|
1333
|
+
обновляет все документы в коллекции.
|
|
1334
|
+
|
|
1335
|
+
Сигнатура:
|
|
1336
|
+
|
|
1337
|
+
```ts
|
|
1338
|
+
patch(
|
|
1339
|
+
data: PartialWithoutId<FlatData, IdName>,
|
|
1340
|
+
where?: WhereClause<FlatData>,
|
|
1341
|
+
): Promise<number>;
|
|
1342
|
+
```
|
|
1343
|
+
|
|
1344
|
+
**Примеры**
|
|
1345
|
+
|
|
1346
|
+
Обновление документов по условию.
|
|
1347
|
+
|
|
1348
|
+
```js
|
|
1349
|
+
// обновит все товары с ценой меньше 30
|
|
1350
|
+
const updatedCount = await productRep.patch(
|
|
1351
|
+
{inStock: false},
|
|
1352
|
+
{price: {lt: 30}},
|
|
1353
|
+
);
|
|
1354
|
+
```
|
|
1355
|
+
|
|
1356
|
+
Обновление всех документов.
|
|
1357
|
+
|
|
1358
|
+
```js
|
|
1359
|
+
// добавит или обновит поле updatedAt для всех документов
|
|
1360
|
+
const totalCount = await productRep.patch({
|
|
1361
|
+
updatedAt: new Date(),
|
|
1362
|
+
});
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
### repository.find
|
|
1366
|
+
|
|
1367
|
+
Находит все документы, соответствующие условиям фильтрации, и возвращает их
|
|
1368
|
+
в виде массива. Если фильтр не указан, возвращает все документы коллекции.
|
|
1369
|
+
|
|
1370
|
+
Сигнатура:
|
|
1371
|
+
|
|
1372
|
+
```ts
|
|
1373
|
+
find(filter?: FilterClause<FlatData>): Promise<FlatData[]>;
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
**Примеры**
|
|
1377
|
+
|
|
1378
|
+
Поиск всех документов.
|
|
1379
|
+
|
|
1380
|
+
```js
|
|
1381
|
+
const allProducts = await productRep.find();
|
|
1382
|
+
```
|
|
1383
|
+
|
|
1384
|
+
Поиск документов по условию `where`.
|
|
1385
|
+
|
|
1386
|
+
```js
|
|
1387
|
+
const cheapProducts = await productRep.find({
|
|
1388
|
+
where: {price: {lt: 100}},
|
|
1389
|
+
});
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
Поиск с сортировкой и ограничением выборки.
|
|
1393
|
+
|
|
1394
|
+
```js
|
|
1395
|
+
const latestProducts = await productRep.find({
|
|
1396
|
+
order: 'createdAt DESC',
|
|
1397
|
+
limit: 10,
|
|
1398
|
+
});
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
### repository.findOne
|
|
1402
|
+
|
|
1403
|
+
Находит первый документ, соответствующий условиям фильтрации. Возвращает
|
|
1404
|
+
`undefined`, если документы не найдены.
|
|
1405
|
+
|
|
1406
|
+
Сигнатура:
|
|
1407
|
+
|
|
1408
|
+
```ts
|
|
1409
|
+
findOne(
|
|
1410
|
+
filter?: FilterClause<FlatData>,
|
|
1411
|
+
): Promise<FlatData | undefined>;
|
|
1412
|
+
```
|
|
1413
|
+
|
|
1414
|
+
**Примеры**
|
|
1415
|
+
|
|
1416
|
+
Поиск одного документа по условию.
|
|
1417
|
+
|
|
1418
|
+
```js
|
|
1419
|
+
const expensiveProduct = await productRep.findOne({
|
|
1420
|
+
where: {price: {gt: 1000}},
|
|
1421
|
+
order: 'price DESC',
|
|
1422
|
+
});
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
Обработка случая, когда документ не найден.
|
|
1032
1426
|
|
|
1033
1427
|
```js
|
|
1034
|
-
const
|
|
1428
|
+
const product = await productRep.findOne({
|
|
1429
|
+
where: {name: 'Non-existent Product'},
|
|
1430
|
+
});
|
|
1431
|
+
if (!product) {
|
|
1432
|
+
console.log('Product not found.');
|
|
1433
|
+
}
|
|
1434
|
+
```
|
|
1435
|
+
|
|
1436
|
+
### repository.findById
|
|
1437
|
+
|
|
1438
|
+
Находит один документ по его уникальному идентификатору. Если документ не
|
|
1439
|
+
найден, выбрасывается ошибка.
|
|
1035
1440
|
|
|
1036
|
-
|
|
1441
|
+
Сигнатура:
|
|
1442
|
+
|
|
1443
|
+
```ts
|
|
1444
|
+
findById(
|
|
1445
|
+
id: IdType,
|
|
1446
|
+
filter?: ItemFilterClause<FlatData>,
|
|
1447
|
+
): Promise<FlatData>;
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
**Примеры**
|
|
1451
|
+
|
|
1452
|
+
Поиск документа по `id`.
|
|
1453
|
+
|
|
1454
|
+
```js
|
|
1455
|
+
try {
|
|
1456
|
+
const product = await productRep.findById(1);
|
|
1457
|
+
console.log(product);
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
console.error('Product with id 1 is not found.');
|
|
1460
|
+
}
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
Поиск документа с включением связанных данных.
|
|
1464
|
+
|
|
1465
|
+
```js
|
|
1466
|
+
const product = await productRep.findById(1, {
|
|
1467
|
+
include: 'category',
|
|
1468
|
+
});
|
|
1469
|
+
```
|
|
1470
|
+
|
|
1471
|
+
### repository.delete
|
|
1472
|
+
|
|
1473
|
+
Удаляет один или несколько документов, соответствующих условиям `where`.
|
|
1474
|
+
Возвращает количество удаленных документов. Если `where` не указан, удаляет
|
|
1475
|
+
все документы в коллекции.
|
|
1476
|
+
|
|
1477
|
+
Сигнатура:
|
|
1478
|
+
|
|
1479
|
+
```ts
|
|
1480
|
+
delete(where?: WhereClause<FlatData>): Promise<number>;
|
|
1481
|
+
```
|
|
1482
|
+
|
|
1483
|
+
**Примеры**
|
|
1484
|
+
|
|
1485
|
+
Удаление документов по условию.
|
|
1486
|
+
|
|
1487
|
+
```js
|
|
1488
|
+
const deletedCount = await productRep.delete({
|
|
1489
|
+
inStock: false,
|
|
1490
|
+
});
|
|
1491
|
+
```
|
|
1492
|
+
|
|
1493
|
+
Удаление всех документов.
|
|
1494
|
+
|
|
1495
|
+
```js
|
|
1496
|
+
const totalCount = await productRep.delete();
|
|
1497
|
+
```
|
|
1498
|
+
|
|
1499
|
+
### repository.deleteById
|
|
1500
|
+
|
|
1501
|
+
Удаляет один документ по его уникальному идентификатору. Возвращает `true`,
|
|
1502
|
+
если документ был найден и удален, в противном случае `false`.
|
|
1503
|
+
|
|
1504
|
+
Сигнатура:
|
|
1505
|
+
|
|
1506
|
+
```ts
|
|
1507
|
+
deleteById(id: IdType): Promise<boolean>;
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
**Примеры**
|
|
1511
|
+
|
|
1512
|
+
Удаление документа по `id`.
|
|
1513
|
+
|
|
1514
|
+
```js
|
|
1515
|
+
const wasDeleted = await productRep.deleteById(1);
|
|
1516
|
+
if (wasDeleted) {
|
|
1517
|
+
console.log('The document was deleted.');
|
|
1518
|
+
} else {
|
|
1519
|
+
console.log('No document found to delete.');
|
|
1520
|
+
}
|
|
1521
|
+
```
|
|
1522
|
+
|
|
1523
|
+
### repository.exists
|
|
1524
|
+
|
|
1525
|
+
Проверяет существование документа с указанным идентификатором. Возвращает
|
|
1526
|
+
`true`, если документ существует, иначе `false`.
|
|
1527
|
+
|
|
1528
|
+
Сигнатура:
|
|
1529
|
+
|
|
1530
|
+
```ts
|
|
1531
|
+
exists(id: IdType): Promise<boolean>;
|
|
1532
|
+
```
|
|
1533
|
+
|
|
1534
|
+
**Примеры**
|
|
1535
|
+
|
|
1536
|
+
Проверка существования документа по `id`.
|
|
1537
|
+
|
|
1538
|
+
```js
|
|
1539
|
+
const productExists = await productRep.exists(1);
|
|
1540
|
+
if (productExists) {
|
|
1541
|
+
console.log('A document with id 1 exists.');
|
|
1542
|
+
}
|
|
1543
|
+
```
|
|
1544
|
+
|
|
1545
|
+
### repository.count
|
|
1546
|
+
|
|
1547
|
+
Подсчитывает количество документов, соответствующих условиям `where`. Если
|
|
1548
|
+
`where` не указан, возвращает общее количество документов в коллекции.
|
|
1549
|
+
|
|
1550
|
+
Сигнатура:
|
|
1551
|
+
|
|
1552
|
+
```ts
|
|
1553
|
+
count(where?: WhereClause<FlatData>): Promise<number>;
|
|
1554
|
+
```
|
|
1555
|
+
|
|
1556
|
+
**Примеры**
|
|
1557
|
+
|
|
1558
|
+
Подсчет документов по условию.
|
|
1559
|
+
|
|
1560
|
+
```js
|
|
1561
|
+
const cheapCount = await productRep.count({
|
|
1562
|
+
price: {lt: 100},
|
|
1563
|
+
});
|
|
1564
|
+
```
|
|
1565
|
+
|
|
1566
|
+
Подсчет всех документов.
|
|
1567
|
+
|
|
1568
|
+
```js
|
|
1569
|
+
const totalCount = await productRep.count();
|
|
1037
1570
|
```
|
|
1038
1571
|
|
|
1039
1572
|
## Фильтрация
|
|
@@ -1488,6 +2021,28 @@ const res = await rep.find({
|
|
|
1488
2021
|
в методах репозитория. Ниже приводится пример автоматического разрешения
|
|
1489
2022
|
связи при использовании метода `findById`.
|
|
1490
2023
|
|
|
2024
|
+
```
|
|
2025
|
+
Роль (role)
|
|
2026
|
+
┌────────────────────┐
|
|
2027
|
+
│ id: 3 <──────────│────┐
|
|
2028
|
+
│ name: 'Manager' │ │
|
|
2029
|
+
└────────────────────┘ │
|
|
2030
|
+
│
|
|
2031
|
+
Пользователь (user) │
|
|
2032
|
+
┌────────────────────────┐ │
|
|
2033
|
+
│ id: 1 │ │
|
|
2034
|
+
│ name: 'John Doe' │ │
|
|
2035
|
+
│ roleId: 3 ──────────│────┘
|
|
2036
|
+
│ cityId: 24 ──────────│────┐
|
|
2037
|
+
└────────────────────────┘ │
|
|
2038
|
+
│
|
|
2039
|
+
Город (city) │
|
|
2040
|
+
┌────────────────────┐ │
|
|
2041
|
+
│ id: 24 <─────────│────┘
|
|
2042
|
+
│ name: 'Moscow' │
|
|
2043
|
+
└────────────────────┘
|
|
2044
|
+
```
|
|
2045
|
+
|
|
1491
2046
|
```js
|
|
1492
2047
|
// запрос документа коллекции "users",
|
|
1493
2048
|
// включая связанные данные (role и city)
|
|
@@ -1519,8 +2074,11 @@ console.log(user);
|
|
|
1519
2074
|
связи можно будет использовать в опции `include` методах репозитория.
|
|
1520
2075
|
|
|
1521
2076
|
```js
|
|
1522
|
-
import {
|
|
1523
|
-
|
|
2077
|
+
import {
|
|
2078
|
+
DataType,
|
|
2079
|
+
RelationType,
|
|
2080
|
+
DatabaseSchema,
|
|
2081
|
+
} from '@e22m4u/js-repository';
|
|
1524
2082
|
|
|
1525
2083
|
dbs.defineModel({
|
|
1526
2084
|
name: 'user',
|
|
@@ -1560,12 +2118,29 @@ dbs.defineModel({
|
|
|
1560
2118
|
*i. Полиморфный режим позволяет динамически определять целевую модель
|
|
1561
2119
|
по ее названию, которое хранит документ в свойстве-дискриминаторе.*
|
|
1562
2120
|
|
|
1563
|
-
|
|
2121
|
+
### Типы связей
|
|
2122
|
+
|
|
2123
|
+
- [Belongs To](#belongs-to)
|
|
2124
|
+
Текущая модель ссылается на целевую по идентификатору.
|
|
2125
|
+
`type: "belongsTo"` или `type: RelationType.BELONGS_TO`
|
|
2126
|
+
|
|
2127
|
+
- [Has One](#has-one)
|
|
2128
|
+
Обратная сторона `belongsTo` по принципу *"один к одному"*.
|
|
2129
|
+
`type: "hasOne"` или `type: RelationType.HAS_ONE`
|
|
1564
2130
|
|
|
1565
|
-
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
2131
|
+
- [Has Many](#has-many)
|
|
2132
|
+
Обратная сторона `belongsTo` по принципу *"один ко многим"*.
|
|
2133
|
+
`type: "hasMany"` или `type: RelationType.HAS_MANY`
|
|
2134
|
+
|
|
2135
|
+
- [References Many](#references-many)
|
|
2136
|
+
Текущая модель ссылается на целевую через массив идентификаторов.
|
|
2137
|
+
`type: "referencesMany"` или `type: RelationType.REFERENCES_MANY`
|
|
2138
|
+
|
|
2139
|
+
Полиморфные версии:
|
|
2140
|
+
|
|
2141
|
+
- [Belongs To (полиморфная)](#belongs-to-полиморфная-версия)
|
|
2142
|
+
- [Has One (полиморфная)](#has-one-полиморфная-версия)
|
|
2143
|
+
- [Has Many (полиморфная)](#has-many-полиморфная-версия)
|
|
1569
2144
|
|
|
1570
2145
|
Параметр `type` в определении связи принимает строку с названием типа. Чтобы исключить опечатку, рекомендуется использовать константы объекта `RelationType`
|
|
1571
2146
|
указанные ниже.
|
|
@@ -1575,9 +2150,25 @@ dbs.defineModel({
|
|
|
1575
2150
|
- `RelationType.HAS_MANY`
|
|
1576
2151
|
- `RelationType.REFERENCES_MANY`
|
|
1577
2152
|
|
|
1578
|
-
|
|
2153
|
+
#### Belongs To
|
|
2154
|
+
|
|
2155
|
+
Текущая модель ссылается на целевую по идентификатору.
|
|
2156
|
+
|
|
2157
|
+
```
|
|
2158
|
+
Текущая (user) Целевая (role)
|
|
2159
|
+
┌─────────────────────────┐ ┌─────────────────────────┐
|
|
2160
|
+
│ id: 1 │ ┌───│─> id: 5 │
|
|
2161
|
+
│ roleId: 5 ───────────│───┤ │ ... │
|
|
2162
|
+
│ ... │ │ └─────────────────────────┘
|
|
2163
|
+
└─────────────────────────┘ │
|
|
2164
|
+
┌─────────────────────────┐ │
|
|
2165
|
+
│ id: 2 │ │
|
|
2166
|
+
│ roleId: 5 ───────────│───┘
|
|
2167
|
+
│ ... │
|
|
2168
|
+
└─────────────────────────┘
|
|
2169
|
+
```
|
|
1579
2170
|
|
|
1580
|
-
|
|
2171
|
+
Определение связи:
|
|
1581
2172
|
|
|
1582
2173
|
```js
|
|
1583
2174
|
dbs.defineModel({
|
|
@@ -1595,24 +2186,191 @@ dbs.defineModel({
|
|
|
1595
2186
|
});
|
|
1596
2187
|
```
|
|
1597
2188
|
|
|
1598
|
-
|
|
2189
|
+
Пример:
|
|
2190
|
+
|
|
2191
|
+
```js
|
|
2192
|
+
import {
|
|
2193
|
+
DataType,
|
|
2194
|
+
RelationType,
|
|
2195
|
+
DatabaseSchema,
|
|
2196
|
+
} from '@e22m4u/js-repository';
|
|
2197
|
+
|
|
2198
|
+
const dbs = new DatabaseSchema();
|
|
2199
|
+
|
|
2200
|
+
// источник данных
|
|
2201
|
+
dbs.defineDatasource({
|
|
2202
|
+
name: 'myDb',
|
|
2203
|
+
adapter: 'memory',
|
|
2204
|
+
});
|
|
2205
|
+
|
|
2206
|
+
// модель роли
|
|
2207
|
+
dbs.defineModel({
|
|
2208
|
+
name: 'role',
|
|
2209
|
+
datasource: 'myDb',
|
|
2210
|
+
properties: {
|
|
2211
|
+
name: DataType.STRING,
|
|
2212
|
+
},
|
|
2213
|
+
});
|
|
2214
|
+
|
|
2215
|
+
// модель пользователя
|
|
2216
|
+
dbs.defineModel({
|
|
2217
|
+
name: 'user',
|
|
2218
|
+
datasource: 'myDb',
|
|
2219
|
+
properties: {
|
|
2220
|
+
name: DataType.STRING,
|
|
2221
|
+
roleId: DataType.NUMBER, // не обязательно
|
|
2222
|
+
},
|
|
2223
|
+
relations: {
|
|
2224
|
+
role: {
|
|
2225
|
+
type: RelationType.BELONGS_TO,
|
|
2226
|
+
model: 'role',
|
|
2227
|
+
foreignKey: 'roleId', // не обязательно
|
|
2228
|
+
},
|
|
2229
|
+
},
|
|
2230
|
+
});
|
|
2231
|
+
|
|
2232
|
+
// создание роли
|
|
2233
|
+
const roleRep = dbs.getRepository('role');
|
|
2234
|
+
const role = await roleRep.create({
|
|
2235
|
+
id: 5,
|
|
2236
|
+
name: 'Manager',
|
|
2237
|
+
});
|
|
2238
|
+
console.log(role);
|
|
2239
|
+
// {
|
|
2240
|
+
// id: 5,
|
|
2241
|
+
// name: 'manager'
|
|
2242
|
+
// }
|
|
2243
|
+
|
|
2244
|
+
// создание пользователя
|
|
2245
|
+
const userRep = dbs.getRepository('user');
|
|
2246
|
+
const user = await userRep.create({
|
|
2247
|
+
id: 1,
|
|
2248
|
+
name: 'John Doe',
|
|
2249
|
+
roleId: role.id,
|
|
2250
|
+
});
|
|
2251
|
+
console.log(user);
|
|
2252
|
+
// {
|
|
2253
|
+
// id: 1,
|
|
2254
|
+
// name: 'John Doe',
|
|
2255
|
+
// roleId: 5
|
|
2256
|
+
// }
|
|
2257
|
+
|
|
2258
|
+
// извлечение пользователя и связанной роли (опция "include")
|
|
2259
|
+
const userWithRole = await userRep.findById(user.id, {include: 'role'});
|
|
2260
|
+
console.log(userWithRole);
|
|
2261
|
+
// {
|
|
2262
|
+
// id: 1,
|
|
2263
|
+
// name: 'John Doe',
|
|
2264
|
+
// roleId: 5,
|
|
2265
|
+
// role: {
|
|
2266
|
+
// id: 5,
|
|
2267
|
+
// name: 'Manager'
|
|
2268
|
+
// }
|
|
2269
|
+
// }
|
|
2270
|
+
```
|
|
2271
|
+
|
|
2272
|
+
#### Has One
|
|
2273
|
+
|
|
2274
|
+
Обратная сторона `belongsTo` по принципу *"один к одному"*.
|
|
2275
|
+
|
|
2276
|
+
```
|
|
2277
|
+
Текущая (profile) Целевая (user)
|
|
2278
|
+
┌─────────────────────────┐ ┌─────────────────────────┐
|
|
2279
|
+
│ id: 5 <──────────────│───┐ │ id: 1 │
|
|
2280
|
+
│ ... │ └───│── profileId: 5 │
|
|
2281
|
+
└─────────────────────────┘ │ ... │
|
|
2282
|
+
└─────────────────────────┘
|
|
2283
|
+
```
|
|
2284
|
+
|
|
2285
|
+
Определение связи:
|
|
2286
|
+
|
|
2287
|
+
```js
|
|
2288
|
+
// dbs.defineModel({
|
|
2289
|
+
// name: 'user',
|
|
2290
|
+
// relations: {
|
|
2291
|
+
// profile: {
|
|
2292
|
+
// type: RelationType.BELONGS_TO,
|
|
2293
|
+
// model: 'profile',
|
|
2294
|
+
// },
|
|
2295
|
+
// },
|
|
2296
|
+
// });
|
|
2297
|
+
|
|
2298
|
+
dbs.defineModel({
|
|
2299
|
+
name: 'profile',
|
|
2300
|
+
relations: {
|
|
2301
|
+
user: { // название связи
|
|
2302
|
+
type: RelationType.HAS_ONE, // целевая модель ссылается на текущую
|
|
2303
|
+
model: 'user', // название целевой модели
|
|
2304
|
+
foreignKey: 'profileId', // внешний ключ из целевой модели на текущую
|
|
2305
|
+
},
|
|
2306
|
+
},
|
|
2307
|
+
});
|
|
2308
|
+
```
|
|
2309
|
+
|
|
2310
|
+
#### Has Many
|
|
2311
|
+
|
|
2312
|
+
Обратная сторона `belongsTo` по принципу *"один ко многим"*.
|
|
2313
|
+
|
|
2314
|
+
```
|
|
2315
|
+
Текущая (role) Целевая (user)
|
|
2316
|
+
┌─────────────────────────┐ ┌─────────────────────────┐
|
|
2317
|
+
│ id: 5 <──────────────│───┐ │ id: 1 │
|
|
2318
|
+
│ ... │ ├───│── roleId: 5 │
|
|
2319
|
+
└─────────────────────────┘ │ │ ... │
|
|
2320
|
+
│ └─────────────────────────┘
|
|
2321
|
+
│ ┌─────────────────────────┐
|
|
2322
|
+
│ │ id: 2 │
|
|
2323
|
+
└───│── roleId: 5 │
|
|
2324
|
+
│ ... │
|
|
2325
|
+
└─────────────────────────┘
|
|
2326
|
+
```
|
|
2327
|
+
|
|
2328
|
+
Определение связи:
|
|
1599
2329
|
|
|
1600
2330
|
```js
|
|
2331
|
+
// dbs.defineModel({
|
|
2332
|
+
// name: 'user',
|
|
2333
|
+
// relations: {
|
|
2334
|
+
// role: {
|
|
2335
|
+
// type: RelationType.BELONGS_TO,
|
|
2336
|
+
// model: 'role',
|
|
2337
|
+
// },
|
|
2338
|
+
// },
|
|
2339
|
+
// });
|
|
2340
|
+
|
|
1601
2341
|
dbs.defineModel({
|
|
1602
2342
|
name: 'role',
|
|
1603
2343
|
relations: {
|
|
1604
2344
|
users: { // название связи
|
|
1605
2345
|
type: RelationType.HAS_MANY, // целевая модель ссылается на текущую
|
|
1606
2346
|
model: 'user', // название целевой модели
|
|
1607
|
-
foreignKey: 'roleId', // внешний ключ
|
|
2347
|
+
foreignKey: 'roleId', // внешний ключ целевой модели
|
|
1608
2348
|
},
|
|
1609
2349
|
},
|
|
1610
2350
|
});
|
|
1611
2351
|
```
|
|
1612
2352
|
|
|
1613
|
-
|
|
2353
|
+
#### References Many
|
|
2354
|
+
|
|
2355
|
+
Текущая модель ссылается на целевую через массив идентификаторов.
|
|
2356
|
+
|
|
2357
|
+
```
|
|
2358
|
+
Текущая (article) Целевая (category)
|
|
2359
|
+
┌─────────────────────────┐ ┌─────────────────────────┐
|
|
2360
|
+
│ id: 1 │ ┌───│─> id: 5 │
|
|
2361
|
+
│ categoryIds: [5, 6] ──│───┤ │ ... │
|
|
2362
|
+
│ ... │ │ └─────────────────────────┘
|
|
2363
|
+
└─────────────────────────┘ │ ┌─────────────────────────┐
|
|
2364
|
+
└───│─> id: 6 │
|
|
2365
|
+
│ ... │
|
|
2366
|
+
└─────────────────────────┘
|
|
2367
|
+
```
|
|
2368
|
+
|
|
2369
|
+
Определение связи:
|
|
1614
2370
|
|
|
1615
2371
|
```js
|
|
2372
|
+
// dbs.defineModel({name: 'category', ...
|
|
2373
|
+
|
|
1616
2374
|
dbs.defineModel({
|
|
1617
2375
|
name: 'article',
|
|
1618
2376
|
relations: {
|
|
@@ -1628,7 +2386,27 @@ dbs.defineModel({
|
|
|
1628
2386
|
});
|
|
1629
2387
|
```
|
|
1630
2388
|
|
|
1631
|
-
|
|
2389
|
+
#### Belongs To (полиморфная версия)
|
|
2390
|
+
|
|
2391
|
+
Текущая модель ссылается на целевую по идентификатору. Название целевой модели
|
|
2392
|
+
определяется свойством-дискриминатором.
|
|
2393
|
+
|
|
2394
|
+
```
|
|
2395
|
+
Текущая (file) ┌──────> Целевая 1 (letter)
|
|
2396
|
+
┌─────────────────────────────┐ │ ┌─────────────────────────┐
|
|
2397
|
+
│ id: 1 │ │ ┌──│─> id: 10 │
|
|
2398
|
+
│ referenceType: 'letter' ─│──┘ │ │ ... │
|
|
2399
|
+
│ referenceId: 10 ─────────│────┘ └─────────────────────────┘
|
|
2400
|
+
└─────────────────────────────┘
|
|
2401
|
+
┌──────> Целевая 2 (user)
|
|
2402
|
+
┌─────────────────────────────┐ │ ┌─────────────────────────┐
|
|
2403
|
+
│ id: 2 │ │ ┌──│─> id: 5 │
|
|
2404
|
+
│ referenceType: 'user' ───│──┘ │ │ ... │
|
|
2405
|
+
│ referenceId: 5 ──────────│────┘ └─────────────────────────┘
|
|
2406
|
+
└─────────────────────────────┘
|
|
2407
|
+
```
|
|
2408
|
+
|
|
2409
|
+
Определение связи:
|
|
1632
2410
|
|
|
1633
2411
|
```js
|
|
1634
2412
|
dbs.defineModel({
|
|
@@ -1647,7 +2425,7 @@ dbs.defineModel({
|
|
|
1647
2425
|
});
|
|
1648
2426
|
```
|
|
1649
2427
|
|
|
1650
|
-
|
|
2428
|
+
Определение связи с указанием свойств:
|
|
1651
2429
|
|
|
1652
2430
|
```js
|
|
1653
2431
|
dbs.defineModel({
|
|
@@ -1663,9 +2441,104 @@ dbs.defineModel({
|
|
|
1663
2441
|
});
|
|
1664
2442
|
```
|
|
1665
2443
|
|
|
1666
|
-
|
|
2444
|
+
#### Has One (полиморфная версия)
|
|
2445
|
+
|
|
2446
|
+
Обратная сторона полиморфная связи `belongsTo` по принципу *"один к одному"*.
|
|
2447
|
+
|
|
2448
|
+
```
|
|
2449
|
+
Текущая (company) <───────┐ Целевая (license)
|
|
2450
|
+
┌─────────────────────────┐ │ ┌─────────────────────────┐
|
|
2451
|
+
│ id: 10 <─────────────│──┐ │ │ id: 1 │
|
|
2452
|
+
│ ... │ │ └──│── ownerType: 'company' │
|
|
2453
|
+
└─────────────────────────┘ └────│── ownerId: 10 │
|
|
2454
|
+
└─────────────────────────┘
|
|
2455
|
+
```
|
|
2456
|
+
|
|
2457
|
+
Определение связи с указанием названия связи целевой модели:
|
|
2458
|
+
|
|
2459
|
+
```js
|
|
2460
|
+
// dbs.defineModel({
|
|
2461
|
+
// name: 'license',
|
|
2462
|
+
// relations: {
|
|
2463
|
+
// owner: {
|
|
2464
|
+
// type: RelationType.BELONGS_TO,
|
|
2465
|
+
// polymorphic: true,
|
|
2466
|
+
// },
|
|
2467
|
+
// },
|
|
2468
|
+
// });
|
|
2469
|
+
|
|
2470
|
+
dbs.defineModel({
|
|
2471
|
+
name: 'company',
|
|
2472
|
+
relations: {
|
|
2473
|
+
license: { // название связи
|
|
2474
|
+
type: RelationType.HAS_ONE, // целевая модель ссылается на текущую
|
|
2475
|
+
model: 'license', // название целевой модели
|
|
2476
|
+
polymorphic: 'owner', // название полиморфной связи целевой модели
|
|
2477
|
+
},
|
|
2478
|
+
},
|
|
2479
|
+
});
|
|
2480
|
+
```
|
|
2481
|
+
|
|
2482
|
+
Определение связи с указанием свойств целевой модели:
|
|
2483
|
+
|
|
2484
|
+
```js
|
|
2485
|
+
// dbs.defineModel({
|
|
2486
|
+
// name: 'license',
|
|
2487
|
+
// relations: {
|
|
2488
|
+
// owner: {
|
|
2489
|
+
// type: RelationType.BELONGS_TO,
|
|
2490
|
+
// polymorphic: true,
|
|
2491
|
+
// foreignKey: 'ownerId',
|
|
2492
|
+
// discriminator: 'ownerType',
|
|
2493
|
+
// },
|
|
2494
|
+
// },
|
|
2495
|
+
// });
|
|
2496
|
+
|
|
2497
|
+
dbs.defineModel({
|
|
2498
|
+
name: 'company',
|
|
2499
|
+
relations: {
|
|
2500
|
+
license: { // название связи
|
|
2501
|
+
type: RelationType.HAS_ONE, // целевая модель ссылается на текущую
|
|
2502
|
+
model: 'license', // название целевой модели
|
|
2503
|
+
polymorphic: true, // название текущей модели находится в дискриминаторе
|
|
2504
|
+
foreignKey: 'ownerId', // свойство целевой модели для идентификатора
|
|
2505
|
+
discriminator: 'ownerType', // свойство целевой модели для названия текущей
|
|
2506
|
+
},
|
|
2507
|
+
},
|
|
2508
|
+
});
|
|
2509
|
+
```
|
|
2510
|
+
|
|
2511
|
+
#### Has Many (полиморфная версия)
|
|
2512
|
+
|
|
2513
|
+
Обратная сторона полиморфная связи `belongsTo` по принципу *"один ко многим"*.
|
|
2514
|
+
|
|
2515
|
+
```
|
|
2516
|
+
Текущая (letter) <─────────┐ Целевая (file)
|
|
2517
|
+
┌──────────────────────────┐ │ ┌────────────────────────────┐
|
|
2518
|
+
│ id: 10 <──────────────│──┐ │ │ id: 1 │
|
|
2519
|
+
│ ... │ │ ├──│── referenceType: 'letter' │
|
|
2520
|
+
└──────────────────────────┘ ├─│──│── referenceId: 10 │
|
|
2521
|
+
│ │ └────────────────────────────┘
|
|
2522
|
+
│ │ ┌────────────────────────────┐
|
|
2523
|
+
│ │ │ id: 2 │
|
|
2524
|
+
│ └──│── referenceType: 'letter' │
|
|
2525
|
+
└────│── referenceId: 10 │
|
|
2526
|
+
└────────────────────────────┘
|
|
2527
|
+
```
|
|
2528
|
+
|
|
2529
|
+
Определение связи с указанием названия связи целевой модели:
|
|
1667
2530
|
|
|
1668
2531
|
```js
|
|
2532
|
+
// dbs.defineModel({
|
|
2533
|
+
// name: 'file',
|
|
2534
|
+
// relations: {
|
|
2535
|
+
// reference: {
|
|
2536
|
+
// type: RelationType.BELONGS_TO,
|
|
2537
|
+
// polymorphic: true,
|
|
2538
|
+
// },
|
|
2539
|
+
// },
|
|
2540
|
+
// });
|
|
2541
|
+
|
|
1669
2542
|
dbs.defineModel({
|
|
1670
2543
|
name: 'letter',
|
|
1671
2544
|
relations: {
|
|
@@ -1678,9 +2551,21 @@ dbs.defineModel({
|
|
|
1678
2551
|
});
|
|
1679
2552
|
```
|
|
1680
2553
|
|
|
1681
|
-
|
|
2554
|
+
Определение связи с указанием свойств целевой модели:
|
|
1682
2555
|
|
|
1683
2556
|
```js
|
|
2557
|
+
// dbs.defineModel({
|
|
2558
|
+
// name: 'file',
|
|
2559
|
+
// relations: {
|
|
2560
|
+
// reference: {
|
|
2561
|
+
// type: RelationType.BELONGS_TO,
|
|
2562
|
+
// polymorphic: true,
|
|
2563
|
+
// foreignKey: 'referenceId',
|
|
2564
|
+
// discriminator: 'referenceType',
|
|
2565
|
+
// },
|
|
2566
|
+
// },
|
|
2567
|
+
// });
|
|
2568
|
+
|
|
1684
2569
|
dbs.defineModel({
|
|
1685
2570
|
name: 'letter',
|
|
1686
2571
|
relations: {
|
|
@@ -1733,7 +2618,7 @@ class MyRepository extends Repository {
|
|
|
1733
2618
|
// dbs.defineDatasource ...
|
|
1734
2619
|
// dbs.defineModel ...
|
|
1735
2620
|
|
|
1736
|
-
dbs.
|
|
2621
|
+
dbs.getService(RepositoryRegistry).setRepositoryCtor(MyRepository);
|
|
1737
2622
|
const rep = dbs.getRepository('model');
|
|
1738
2623
|
console.log(rep instanceof MyRepository); // true
|
|
1739
2624
|
```
|
|
@@ -1752,7 +2637,6 @@ import {DatabaseSchema} from '@e22m4u/js-repository';
|
|
|
1752
2637
|
|
|
1753
2638
|
// const dbs = new DatabaseSchema();
|
|
1754
2639
|
// dbs.defineDatasource ...
|
|
1755
|
-
// dbs.defineModel ...
|
|
1756
2640
|
|
|
1757
2641
|
// определение модели "city"
|
|
1758
2642
|
dbs.defineModel({
|
|
@@ -1762,12 +2646,6 @@ dbs.defineModel({
|
|
|
1762
2646
|
name: DataType.STRING,
|
|
1763
2647
|
timeZone: DataType.STRING,
|
|
1764
2648
|
},
|
|
1765
|
-
relations: {
|
|
1766
|
-
country: {
|
|
1767
|
-
type: RelationType.BELONGS_TO,
|
|
1768
|
-
model: 'country',
|
|
1769
|
-
},
|
|
1770
|
-
},
|
|
1771
2649
|
});
|
|
1772
2650
|
|
|
1773
2651
|
// определение интерфейса "city"
|
|
@@ -1775,15 +2653,26 @@ interface City {
|
|
|
1775
2653
|
id: number;
|
|
1776
2654
|
name?: string;
|
|
1777
2655
|
timeZone?: string;
|
|
1778
|
-
countryId?: number;
|
|
1779
|
-
country?: Country;
|
|
1780
2656
|
}
|
|
1781
2657
|
|
|
1782
|
-
//
|
|
1783
|
-
//
|
|
1784
|
-
const cityRep = dbs.getRepository<City
|
|
2658
|
+
// при получении репозитория нужной модели
|
|
2659
|
+
// можно указать тип документов
|
|
2660
|
+
const cityRep = dbs.getRepository<City>('city');
|
|
2661
|
+
|
|
2662
|
+
// теперь, методы репозитория возвращают
|
|
2663
|
+
// тип City вместо Record<string, unknown>
|
|
2664
|
+
const city: City = await cityRep.create({
|
|
2665
|
+
name: 'Moscow',
|
|
2666
|
+
timeZone: 'Europe/Moscow',
|
|
2667
|
+
});
|
|
1785
2668
|
```
|
|
1786
2669
|
|
|
2670
|
+
Для определения моделей с помощью TypeScript классов,
|
|
2671
|
+
рекомендуется использовать специальную версию данного модуля
|
|
2672
|
+
[@e22m4u/ts-repository](https://www.npmjs.com/package/@e22m4u/ts-repository),
|
|
2673
|
+
поставляемую с набором TypeScript декораторов и дополнительных
|
|
2674
|
+
инструментов для работы в TypeScript окружении.
|
|
2675
|
+
|
|
1787
2676
|
## Тесты
|
|
1788
2677
|
|
|
1789
2678
|
```bash
|