@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 CHANGED
@@ -8,8 +8,8 @@
8
8
  npm install @e22m4u/js-repository
9
9
  ```
10
10
 
11
- Опционально устанавливаем адаптер. Например, если используемой базой
12
- является *MongoDB*, то для подключения потребуется добавить
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" с параметром "required"
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 at Avenue Mall",
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.40",
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.1",
40
+ "@commitlint/cli": "^17.7.2",
41
41
  "@commitlint/config-conventional": "^17.7.0",
42
42
  "c8": "^8.0.1",
43
- "chai": "^4.3.7",
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.0",
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.1.0",
51
+ "eslint-plugin-mocha": "^10.2.0",
52
52
  "husky": "^8.0.3",
53
53
  "mocha": "^10.2.0",
54
- "prettier": "^3.0.1",
54
+ "prettier": "^3.0.3",
55
55
  "typescript": "^5.2.2"
56
56
  }
57
57
  }
@@ -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
  *
@@ -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
  *