@e22m4u/js-repository 0.0.40 → 0.0.42
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 +165 -17
- package/package.json +6 -6
- package/src/adapter/adapter.d.ts +13 -0
- package/src/adapter/adapter.js +15 -0
- package/src/adapter/builtin/memory-adapter.d.ts +13 -0
- package/src/adapter/builtin/memory-adapter.js +41 -0
- package/src/adapter/builtin/memory-adapter.spec.js +575 -7
- package/src/adapter/decorator/data-sanitizing-decorator.js +6 -0
- package/src/adapter/decorator/data-sanitizing-decorator.spec.js +13 -0
- package/src/adapter/decorator/data-validation-decorator.js +6 -0
- package/src/adapter/decorator/data-validation-decorator.spec.js +5 -0
- package/src/adapter/decorator/default-values-decorator.js +6 -0
- package/src/adapter/decorator/default-values-decorator.spec.js +46 -0
- package/src/definition/model/relations/relation-definition.d.ts +8 -26
- package/src/definition/model/relations/relations-definition-validator.spec.js +18 -18
- package/src/filter/filter-clause.d.ts +5 -4
- package/src/filter/where-clause-tool.js +6 -2
- package/src/filter/where-clause-tool.spec.js +8 -3
- package/src/repository/repository.d.ts +11 -0
- package/src/repository/repository.js +12 -0
- package/src/repository/repository.spec.js +26 -0
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
npm install @e22m4u/js-repository
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Опционально устанавливаем адаптер. Например, если
|
|
12
|
-
|
|
11
|
+
Опционально устанавливаем адаптер. Например, если используется
|
|
12
|
+
*MongoDB*, то для подключения потребуется установить
|
|
13
13
|
[адаптер mongodb](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter)
|
|
14
14
|
как отдельную зависимость.
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ npm install @e22m4u/js-repository
|
|
|
17
17
|
npm install @e22m4u/js-repository-mongodb-adapter
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
**Список доступных адаптеров:**
|
|
21
21
|
|
|
22
22
|
| адаптер | описание |
|
|
23
23
|
|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
@@ -28,13 +28,13 @@ npm install @e22m4u/js-repository-mongodb-adapter
|
|
|
28
28
|
|
|
29
29
|
Модуль позволяет спроектировать систему связанных данных, доступ к которым
|
|
30
30
|
осуществляется посредством репозиториев. Каждый репозиторий имеет собственную
|
|
31
|
-
|
|
31
|
+
модель данных, которая описывает структуру определенной коллекции в базе,
|
|
32
32
|
а так же определяет связи к другим коллекциям.
|
|
33
33
|
|
|
34
34
|
```mermaid
|
|
35
35
|
flowchart LR
|
|
36
36
|
|
|
37
|
-
A[Datasource]-->B[Model]-->С[Repository];
|
|
37
|
+
A[Datasource]-->B[Data Model]-->С[Repository];
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
## Использование
|
|
@@ -54,7 +54,7 @@ const schema = new Schema();
|
|
|
54
54
|
- `defineModel(modelDef: object): this` - добавить модель
|
|
55
55
|
- `getRepository(modelName: string): Repository` - получить репозиторий
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
### Источник данных
|
|
58
58
|
|
|
59
59
|
Источник описывает способ подключения к базе и используемый адаптер.
|
|
60
60
|
Если адаптер имеет настройки, то они передаются вместе с объектом
|
|
@@ -72,7 +72,7 @@ schema.defineDatasource({
|
|
|
72
72
|
});
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
**Параметры источника:**
|
|
76
76
|
|
|
77
77
|
- `name: string` уникальное название
|
|
78
78
|
- `adapter: string` выбранный адаптер
|
|
@@ -88,7 +88,7 @@ schema.defineDatasource({
|
|
|
88
88
|
});
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
### Модель данных
|
|
92
92
|
|
|
93
93
|
Когда источники определены, можно перейти к описанию моделей данных.
|
|
94
94
|
Модель может определять как структуру какого-либо объекта,
|
|
@@ -109,7 +109,7 @@ schema.defineModel({
|
|
|
109
109
|
});
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
**Параметры модели:**
|
|
113
113
|
|
|
114
114
|
- `name: string` уникальное название (обязательно)
|
|
115
115
|
- `datasource: string` выбранный источник данных
|
|
@@ -124,15 +124,15 @@ schema.defineModel({
|
|
|
124
124
|
name: 'latLng',
|
|
125
125
|
properties: {
|
|
126
126
|
lat: DataType.NUMBER, // краткое определение поля "lat"
|
|
127
|
-
lng: { // определение поля "lng"
|
|
128
|
-
type: DataType.NUMBER,
|
|
127
|
+
lng: { // расширенное определение поля "lng"
|
|
128
|
+
type: DataType.NUMBER, // тип допустимого значения
|
|
129
129
|
required: true, // исключает null и undefined
|
|
130
130
|
},
|
|
131
131
|
},
|
|
132
132
|
});
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
**Типы данных:**
|
|
136
136
|
|
|
137
137
|
- `DataType.ANY`
|
|
138
138
|
- `DataType.STRING`
|
|
@@ -167,7 +167,7 @@ schema.defineModel({
|
|
|
167
167
|
```json
|
|
168
168
|
{
|
|
169
169
|
"id": 1,
|
|
170
|
-
"name": "Burger King
|
|
170
|
+
"name": "Burger King",
|
|
171
171
|
"location": {
|
|
172
172
|
"lat": 32.412891,
|
|
173
173
|
"lng": 34.7660061
|
|
@@ -186,7 +186,7 @@ schema.defineModel({
|
|
|
186
186
|
});
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
**Параметры поля:**
|
|
190
190
|
|
|
191
191
|
- `type: string` тип допустимого значения (обязательно)
|
|
192
192
|
- `itemType: string` тип элемента массива (для `type: 'array'`)
|
|
@@ -197,7 +197,7 @@ schema.defineModel({
|
|
|
197
197
|
- `required: boolean` объявить поле обязательным
|
|
198
198
|
- `default: any` значение по умолчанию
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
### Репозиторий
|
|
201
201
|
|
|
202
202
|
В отличие от `latLng`, модель `place` имеет источник данных с названием
|
|
203
203
|
`myMemory`, который был объявлен ранее. Наличие источника позволяет получить
|
|
@@ -207,6 +207,153 @@ schema.defineModel({
|
|
|
207
207
|
const rep = schema.getRepository('place');
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
+
**Методы:**
|
|
211
|
+
|
|
212
|
+
- `create(data, filter = undefined)`
|
|
213
|
+
- `replaceById(id, data, filter = undefined)`
|
|
214
|
+
- `replaceOrCreate(data, filter = undefined)`
|
|
215
|
+
- `patch(data, where = undefined)`
|
|
216
|
+
- `patchById(id, data, filter = undefined)`
|
|
217
|
+
- `find(filter = undefined)`
|
|
218
|
+
- `findOne(filter = undefined)`
|
|
219
|
+
- `findById(id, filter = undefined)`
|
|
220
|
+
- `delete(where = undefined)`
|
|
221
|
+
- `deleteById(id)`
|
|
222
|
+
- `exists(id)`
|
|
223
|
+
- `count(where = undefined)`
|
|
224
|
+
|
|
225
|
+
#### create(data, filter = undefined)
|
|
226
|
+
|
|
227
|
+
Создадим торговую точку методом `create` используя репозиторий из примера
|
|
228
|
+
выше. Метод возвращает документ, который был записан в базу, включая присвоенный
|
|
229
|
+
идентификатор.
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
const place = await rep.create({
|
|
233
|
+
"name": "Burger King",
|
|
234
|
+
"location": {
|
|
235
|
+
"lat": 32.412891,
|
|
236
|
+
"lng": 34.7660061
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
console.log(place);
|
|
241
|
+
// {
|
|
242
|
+
// "id": 1,
|
|
243
|
+
// "name": "Burger King",
|
|
244
|
+
// "location": {
|
|
245
|
+
// "lat": 32.412891,
|
|
246
|
+
// "lng": 34.7660061
|
|
247
|
+
// }
|
|
248
|
+
// }
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### replaceById(id, data, filter = undefined)
|
|
252
|
+
|
|
253
|
+
Добавленный в базу документ можно полностью заменить зная его идентификатор.
|
|
254
|
+
Воспользуемся методом `replaceById`, который перезапишет данные по значению
|
|
255
|
+
первичного ключа.
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
// {
|
|
259
|
+
// "id": 1,
|
|
260
|
+
// "name": "Burger King",
|
|
261
|
+
// "location": {
|
|
262
|
+
// "lat": 32.412891,
|
|
263
|
+
// "lng": 34.7660061
|
|
264
|
+
// }
|
|
265
|
+
// }
|
|
266
|
+
const result = rep.replaceById(place.id, {
|
|
267
|
+
name: 'Starbucks',
|
|
268
|
+
address: 'Sukhumvit Alley'
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
console.log(result);
|
|
272
|
+
// {
|
|
273
|
+
// "id": 1,
|
|
274
|
+
// "name": "Starbucks",
|
|
275
|
+
// "address": "Sukhumvit Alley"
|
|
276
|
+
// }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### replaceOrCreate(data, filter = undefined)
|
|
280
|
+
|
|
281
|
+
Если вы знакомы с методом PUT в архитектуре REST, когда документ
|
|
282
|
+
добавляется, если его не существовало, или же обновляется существующий,
|
|
283
|
+
то `replaceOrCreate` работает схожим образом. Если параметр
|
|
284
|
+
`data` передаваемый первым аргументом будет содержать идентификатор,
|
|
285
|
+
то метод будет вести себя как `replaceById`, в противном случае будет
|
|
286
|
+
создан новый документ.
|
|
287
|
+
|
|
288
|
+
```js
|
|
289
|
+
// {
|
|
290
|
+
// "id": 1,
|
|
291
|
+
// "name": "Starbucks",
|
|
292
|
+
// "address": "Sukhumvit Alley"
|
|
293
|
+
// }
|
|
294
|
+
const result = rep.replaceOrCreate({
|
|
295
|
+
id: 1,
|
|
296
|
+
name: 'Airport',
|
|
297
|
+
city: 'Antalya',
|
|
298
|
+
code: 'AYT'
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
console.log(result);
|
|
302
|
+
// {
|
|
303
|
+
// "id": 1,
|
|
304
|
+
// "name": "Airport",
|
|
305
|
+
// "city": "Antalya"
|
|
306
|
+
// "code": "AYT"
|
|
307
|
+
// }
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
В примере выше был передан первичный ключ `id` для поиска и
|
|
311
|
+
замены существующего документа. Теперь рассмотрим создание
|
|
312
|
+
документа с новым идентификатором.
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
const result = rep.replaceOrCreate({
|
|
316
|
+
name: 'Airport',
|
|
317
|
+
city: 'Bangkok',
|
|
318
|
+
code: 'BKK',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
console.log(result);
|
|
322
|
+
// {
|
|
323
|
+
// "id": 2,
|
|
324
|
+
// "name": "Airport",
|
|
325
|
+
// "city": "Bangkok",
|
|
326
|
+
// "code": "BKK"
|
|
327
|
+
// }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### patchById(id, data, filter = undefined)
|
|
331
|
+
|
|
332
|
+
В отличие от `replaceById`, данный метод не удаляет поля, которые
|
|
333
|
+
не были переданы, что позволяет обновить только часть документа,
|
|
334
|
+
не затрагивая другие данные.
|
|
335
|
+
|
|
336
|
+
```js
|
|
337
|
+
// {
|
|
338
|
+
// "id": 2,
|
|
339
|
+
// "name": "Airport",
|
|
340
|
+
// "city": "Bangkok",
|
|
341
|
+
// "code": "BKK"
|
|
342
|
+
// }
|
|
343
|
+
const result = rep.patchById(place.id, {
|
|
344
|
+
city: 'Moscow',
|
|
345
|
+
code: 'SVO'
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
console.log(result);
|
|
349
|
+
// {
|
|
350
|
+
// "id": 2,
|
|
351
|
+
// "name": "Airport",
|
|
352
|
+
// "city": "Moscow",
|
|
353
|
+
// "code": "SVO"
|
|
354
|
+
// }
|
|
355
|
+
```
|
|
356
|
+
|
|
210
357
|
## Пример
|
|
211
358
|
|
|
212
359
|
Создаем модель `user`
|
|
@@ -275,6 +422,7 @@ await userRep.deleteById(fedor.id); // true
|
|
|
275
422
|
- `create(data, filter = undefined)`
|
|
276
423
|
- `replaceById(id, data, filter = undefined)`
|
|
277
424
|
- `replaceOrCreate(data, filter = undefined)`
|
|
425
|
+
- `patch(data, where = undefined)`
|
|
278
426
|
- `patchById(id, data, filter = undefined)`
|
|
279
427
|
- `find(filter = undefined)`
|
|
280
428
|
- `findOne(filter = undefined)`
|
|
@@ -573,7 +721,7 @@ schema.defineModel({
|
|
|
573
721
|
order: {
|
|
574
722
|
type: 'hasOne',
|
|
575
723
|
model: 'order',
|
|
576
|
-
foreignKey: 'customerId', //
|
|
724
|
+
foreignKey: 'customerId', // поле целевой модели
|
|
577
725
|
},
|
|
578
726
|
},
|
|
579
727
|
});
|
|
@@ -625,7 +773,7 @@ schema.defineModel({
|
|
|
625
773
|
orders: {
|
|
626
774
|
type: 'hasMany',
|
|
627
775
|
model: 'order',
|
|
628
|
-
foreignKey: 'customerId', //
|
|
776
|
+
foreignKey: 'customerId', // поле целевой модели
|
|
629
777
|
},
|
|
630
778
|
},
|
|
631
779
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e22m4u/js-repository",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.42",
|
|
4
4
|
"description": "Абстракция для работы с базами данных для Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -37,21 +37,21 @@
|
|
|
37
37
|
"@e22m4u/js-format": "0.0.7"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@commitlint/cli": "^17.7.
|
|
40
|
+
"@commitlint/cli": "^17.7.2",
|
|
41
41
|
"@commitlint/config-conventional": "^17.7.0",
|
|
42
42
|
"c8": "^8.0.1",
|
|
43
|
-
"chai": "^4.3.
|
|
43
|
+
"chai": "^4.3.10",
|
|
44
44
|
"chai-as-promised": "^7.1.1",
|
|
45
45
|
"chai-spies": "^1.0.0",
|
|
46
46
|
"chai-subset": "^1.6.0",
|
|
47
|
-
"eslint": "^8.
|
|
47
|
+
"eslint": "^8.51.0",
|
|
48
48
|
"eslint-config-prettier": "^9.0.0",
|
|
49
49
|
"eslint-plugin-chai-expect": "^3.0.0",
|
|
50
50
|
"eslint-plugin-jsdoc": "^46.8.2",
|
|
51
|
-
"eslint-plugin-mocha": "^10.
|
|
51
|
+
"eslint-plugin-mocha": "^10.2.0",
|
|
52
52
|
"husky": "^8.0.3",
|
|
53
53
|
"mocha": "^10.2.0",
|
|
54
|
-
"prettier": "^3.0.
|
|
54
|
+
"prettier": "^3.0.3",
|
|
55
55
|
"typescript": "^5.2.2"
|
|
56
56
|
}
|
|
57
57
|
}
|
package/src/adapter/adapter.d.ts
CHANGED
|
@@ -52,6 +52,19 @@ export declare class Adapter extends Service {
|
|
|
52
52
|
filter?: ItemFilterClause,
|
|
53
53
|
): Promise<ModelData>;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Patch.
|
|
57
|
+
*
|
|
58
|
+
* @param modelName
|
|
59
|
+
* @param modelData
|
|
60
|
+
* @param where
|
|
61
|
+
*/
|
|
62
|
+
patch(
|
|
63
|
+
modelName: string,
|
|
64
|
+
modelData: ModelData,
|
|
65
|
+
where?: WhereClause,
|
|
66
|
+
): Promise<number>;
|
|
67
|
+
|
|
55
68
|
/**
|
|
56
69
|
* Patch by id.
|
|
57
70
|
*
|
package/src/adapter/adapter.js
CHANGED
|
@@ -78,6 +78,21 @@ export class Adapter extends Service {
|
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Patch.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} modelName
|
|
85
|
+
* @param {object} modelData
|
|
86
|
+
* @param {object|undefined} where
|
|
87
|
+
* @returns {Promise<number>}
|
|
88
|
+
*/
|
|
89
|
+
patch(modelName, modelData, where = undefined) {
|
|
90
|
+
throw new NotImplementedError(
|
|
91
|
+
'%s.patch is not implemented.',
|
|
92
|
+
this.constructor.name,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
/**
|
|
82
97
|
* Patch by id.
|
|
83
98
|
*
|
|
@@ -52,6 +52,19 @@ export declare class MemoryAdapter extends Adapter {
|
|
|
52
52
|
filter?: ItemFilterClause,
|
|
53
53
|
): Promise<ModelData>;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Patch.
|
|
57
|
+
*
|
|
58
|
+
* @param modelName
|
|
59
|
+
* @param modelData
|
|
60
|
+
* @param where
|
|
61
|
+
*/
|
|
62
|
+
patch(
|
|
63
|
+
modelName: string,
|
|
64
|
+
modelData: ModelData,
|
|
65
|
+
where?: WhereClause,
|
|
66
|
+
): Promise<number>;
|
|
67
|
+
|
|
55
68
|
/**
|
|
56
69
|
* Patch by id.
|
|
57
70
|
*
|
|
@@ -154,6 +154,47 @@ export class MemoryAdapter extends Adapter {
|
|
|
154
154
|
).convertColumnNamesToPropertyNames(modelName, tableData);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Patch.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} modelName
|
|
161
|
+
* @param {object} modelData
|
|
162
|
+
* @param {object|undefined} where
|
|
163
|
+
* @returns {Promise<number>}
|
|
164
|
+
*/
|
|
165
|
+
async patch(modelName, modelData, where = undefined) {
|
|
166
|
+
const table = this._getTableOrCreate(modelName);
|
|
167
|
+
const tableItems = Array.from(table.values());
|
|
168
|
+
if (!tableItems.length) return 0;
|
|
169
|
+
let modelItems = tableItems.map(tableItem =>
|
|
170
|
+
this.getService(ModelDefinitionUtils).convertColumnNamesToPropertyNames(
|
|
171
|
+
modelName,
|
|
172
|
+
tableItem,
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (where && typeof where === 'object')
|
|
177
|
+
modelItems = this.getService(WhereClauseTool).filter(modelItems, where);
|
|
178
|
+
const size = modelItems.length;
|
|
179
|
+
|
|
180
|
+
const pkPropName =
|
|
181
|
+
this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
|
|
182
|
+
modelName,
|
|
183
|
+
);
|
|
184
|
+
modelData = cloneDeep(modelData);
|
|
185
|
+
delete modelData[pkPropName];
|
|
186
|
+
|
|
187
|
+
modelItems.forEach(existingModelData => {
|
|
188
|
+
const mergedModelData = Object.assign({}, existingModelData, modelData);
|
|
189
|
+
const mergedTableData = this.getService(
|
|
190
|
+
ModelDefinitionUtils,
|
|
191
|
+
).convertPropertyNamesToColumnNames(modelName, mergedModelData);
|
|
192
|
+
const idValue = existingModelData[pkPropName];
|
|
193
|
+
table.set(idValue, mergedTableData);
|
|
194
|
+
});
|
|
195
|
+
return size;
|
|
196
|
+
}
|
|
197
|
+
|
|
157
198
|
/**
|
|
158
199
|
* Patch by id.
|
|
159
200
|
*
|