@e22m4u/js-repository 0.0.39 → 0.0.41

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`
@@ -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,101 @@ 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 at Avenue Mall",
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 at Avenue Mall",
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 at Avenue Mall",
261
+ // "location": {
262
+ // "lat": 32.412891,
263
+ // "lng": 34.7660061
264
+ // }
265
+ // }
266
+ const result = rep.replaceById(place.id, {
267
+ name: 'Terminal 21 Shopping Mall',
268
+ address: 'Sukhumvit 19 Alley'
269
+ });
270
+
271
+ console.log(result);
272
+ // {
273
+ // "id": 1,
274
+ // "name": "Terminal 21 Shopping Mall",
275
+ // "address": "Sukhumvit 19 Alley"
276
+ // }
277
+ ```
278
+
279
+ #### patchById(id, data, filter = undefined)
280
+
281
+ В отличие от `replaceById`, данный метод не удаляет поля, которые
282
+ не были переданы, что позволяет обновить только часть документа,
283
+ не затрагивая другие данные.
284
+
285
+ ```js
286
+ // {
287
+ // "id": 1,
288
+ // "name": "Terminal 21 Shopping Mall",
289
+ // "address": "Sukhumvit 19 Alley"
290
+ // }
291
+ const result = rep.patchById(place.id, {
292
+ address: 'Moo 6',
293
+ city: 'Pattaya',
294
+ });
295
+
296
+ console.log(result);
297
+ // {
298
+ // "id": 1,
299
+ // "name": "Terminal 21 Shopping Mall",
300
+ // "address": "Moo 6",
301
+ // "city": "Pattaya"
302
+ // }
303
+ ```
304
+
210
305
  ## Пример
211
306
 
212
307
  Создаем модель `user`
@@ -275,6 +370,7 @@ await userRep.deleteById(fedor.id); // true
275
370
  - `create(data, filter = undefined)`
276
371
  - `replaceById(id, data, filter = undefined)`
277
372
  - `replaceOrCreate(data, filter = undefined)`
373
+ - `patch(data, where = undefined)`
278
374
  - `patchById(id, data, filter = undefined)`
279
375
  - `find(filter = undefined)`
280
376
  - `findOne(filter = undefined)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/js-repository",
3
- "version": "0.0.39",
3
+ "version": "0.0.41",
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
  *
@@ -1133,6 +1133,574 @@ describe('MemoryAdapter', function () {
1133
1133
  });
1134
1134
  });
1135
1135
 
1136
+ describe('patch', function () {
1137
+ it('updates only provided properties for all items and returns their number', async function () {
1138
+ const schema = new Schema();
1139
+ schema.defineDatasource({
1140
+ name: 'memory',
1141
+ adapter: 'memory',
1142
+ });
1143
+ schema.defineModel({
1144
+ name: 'model',
1145
+ datasource: 'memory',
1146
+ properties: {
1147
+ foo: DataType.STRING,
1148
+ bar: DataType.STRING,
1149
+ },
1150
+ });
1151
+ const adapter = new MemoryAdapter(schema.container, {});
1152
+ const input1 = {foo: 'a1', bar: 'a2'};
1153
+ const input2 = {foo: 'b1', bar: 'b2'};
1154
+ const input3 = {foo: 'c1', bar: 'c2'};
1155
+ const created1 = await adapter.create('model', input1);
1156
+ const created2 = await adapter.create('model', input2);
1157
+ const created3 = await adapter.create('model', input3);
1158
+ const id1 = created1[DEF_PK];
1159
+ const id2 = created2[DEF_PK];
1160
+ const id3 = created3[DEF_PK];
1161
+ const table = adapter._getTableOrCreate('model');
1162
+ const createdItems = Array.from(table.values());
1163
+ expect(createdItems).to.be.eql([
1164
+ {[DEF_PK]: id1, ...input1},
1165
+ {[DEF_PK]: id2, ...input2},
1166
+ {[DEF_PK]: id3, ...input3},
1167
+ ]);
1168
+ const result = await adapter.patch('model', {foo: 'd1'});
1169
+ expect(result).to.be.eq(3);
1170
+ const patchedItems = Array.from(table.values());
1171
+ expect(patchedItems).to.be.eql([
1172
+ {[DEF_PK]: id1, foo: 'd1', bar: 'a2'},
1173
+ {[DEF_PK]: id2, foo: 'd1', bar: 'b2'},
1174
+ {[DEF_PK]: id3, foo: 'd1', bar: 'c2'},
1175
+ ]);
1176
+ });
1177
+
1178
+ it('does not throw an error if a partial data does not have required property', async function () {
1179
+ const schema = new Schema();
1180
+ schema.defineDatasource({
1181
+ name: 'memory',
1182
+ adapter: 'memory',
1183
+ });
1184
+ schema.defineModel({
1185
+ name: 'model',
1186
+ datasource: 'memory',
1187
+ properties: {
1188
+ foo: DataType.STRING,
1189
+ bar: {
1190
+ type: DataType.STRING,
1191
+ required: true,
1192
+ },
1193
+ },
1194
+ });
1195
+ const adapter = new MemoryAdapter(schema.container, {});
1196
+ const input1 = {foo: 'a1', bar: 'a2'};
1197
+ const input2 = {foo: 'b1', bar: 'b2'};
1198
+ const input3 = {foo: 'c1', bar: 'c2'};
1199
+ const created1 = await adapter.create('model', input1);
1200
+ const created2 = await adapter.create('model', input2);
1201
+ const created3 = await adapter.create('model', input3);
1202
+ const id1 = created1[DEF_PK];
1203
+ const id2 = created2[DEF_PK];
1204
+ const id3 = created3[DEF_PK];
1205
+ const table = adapter._getTableOrCreate('model');
1206
+ const createdItems = Array.from(table.values());
1207
+ expect(createdItems).to.be.eql([
1208
+ {[DEF_PK]: id1, ...input1},
1209
+ {[DEF_PK]: id2, ...input2},
1210
+ {[DEF_PK]: id3, ...input3},
1211
+ ]);
1212
+ const result = await adapter.patch('model', {foo: 'd1'});
1213
+ expect(result).to.be.eq(3);
1214
+ const patchedItems = Array.from(table.values());
1215
+ expect(patchedItems).to.be.eql([
1216
+ {[DEF_PK]: id1, foo: 'd1', bar: 'a2'},
1217
+ {[DEF_PK]: id2, foo: 'd1', bar: 'b2'},
1218
+ {[DEF_PK]: id3, foo: 'd1', bar: 'c2'},
1219
+ ]);
1220
+ });
1221
+
1222
+ it('ignores identifier value in a given data in case of a default primary key', async function () {
1223
+ const schema = new Schema();
1224
+ schema.defineDatasource({
1225
+ name: 'memory',
1226
+ adapter: 'memory',
1227
+ });
1228
+ schema.defineModel({
1229
+ name: 'model',
1230
+ datasource: 'memory',
1231
+ properties: {
1232
+ foo: DataType.STRING,
1233
+ bar: DataType.STRING,
1234
+ },
1235
+ });
1236
+ const adapter = new MemoryAdapter(schema.container, {});
1237
+ const input1 = {foo: 'a1', bar: 'a2'};
1238
+ const input2 = {foo: 'b1', bar: 'b2'};
1239
+ const input3 = {foo: 'c1', bar: 'c2'};
1240
+ const created1 = await adapter.create('model', input1);
1241
+ const created2 = await adapter.create('model', input2);
1242
+ const created3 = await adapter.create('model', input3);
1243
+ const id1 = created1[DEF_PK];
1244
+ const id2 = created2[DEF_PK];
1245
+ const id3 = created3[DEF_PK];
1246
+ const table = adapter._getTableOrCreate('model');
1247
+ const createdItems = Array.from(table.values());
1248
+ expect(createdItems).to.be.eql([
1249
+ {[DEF_PK]: id1, ...input1},
1250
+ {[DEF_PK]: id2, ...input2},
1251
+ {[DEF_PK]: id3, ...input3},
1252
+ ]);
1253
+ const result = await adapter.patch('model', {[DEF_PK]: 100, foo: 'd1'});
1254
+ expect(result).to.be.eq(3);
1255
+ const patchedItems = Array.from(table.values());
1256
+ expect(patchedItems).to.be.eql([
1257
+ {[DEF_PK]: id1, foo: 'd1', bar: 'a2'},
1258
+ {[DEF_PK]: id2, foo: 'd1', bar: 'b2'},
1259
+ {[DEF_PK]: id3, foo: 'd1', bar: 'c2'},
1260
+ ]);
1261
+ });
1262
+
1263
+ it('ignores identifier value in a given data in case of a specified primary key', async function () {
1264
+ const schema = new Schema();
1265
+ schema.defineDatasource({
1266
+ name: 'memory',
1267
+ adapter: 'memory',
1268
+ });
1269
+ schema.defineModel({
1270
+ name: 'model',
1271
+ datasource: 'memory',
1272
+ properties: {
1273
+ myId: {
1274
+ type: DataType.NUMBER,
1275
+ primaryKey: true,
1276
+ },
1277
+ foo: DataType.STRING,
1278
+ bar: DataType.STRING,
1279
+ },
1280
+ });
1281
+ const adapter = new MemoryAdapter(schema.container, {});
1282
+ const input1 = {foo: 'a1', bar: 'a2'};
1283
+ const input2 = {foo: 'b1', bar: 'b2'};
1284
+ const input3 = {foo: 'c1', bar: 'c2'};
1285
+ const created1 = await adapter.create('model', input1);
1286
+ const created2 = await adapter.create('model', input2);
1287
+ const created3 = await adapter.create('model', input3);
1288
+ const id1 = created1.myId;
1289
+ const id2 = created2.myId;
1290
+ const id3 = created3.myId;
1291
+ const table = adapter._getTableOrCreate('model');
1292
+ const createdItems = Array.from(table.values());
1293
+ expect(createdItems).to.be.eql([
1294
+ {myId: id1, ...input1},
1295
+ {myId: id2, ...input2},
1296
+ {myId: id3, ...input3},
1297
+ ]);
1298
+ const result = await adapter.patch('model', {myId: 100, foo: 'd1'});
1299
+ expect(result).to.be.eq(3);
1300
+ const patchedItems = Array.from(table.values());
1301
+ expect(patchedItems).to.be.eql([
1302
+ {myId: id1, foo: 'd1', bar: 'a2'},
1303
+ {myId: id2, foo: 'd1', bar: 'b2'},
1304
+ {myId: id3, foo: 'd1', bar: 'c2'},
1305
+ ]);
1306
+ });
1307
+
1308
+ it('sets a default values for patched properties with an undefined value', async function () {
1309
+ const schema = new Schema();
1310
+ schema.defineDatasource({
1311
+ name: 'memory',
1312
+ adapter: 'memory',
1313
+ });
1314
+ schema.defineModel({
1315
+ name: 'model',
1316
+ datasource: 'memory',
1317
+ properties: {
1318
+ foo: {
1319
+ type: DataType.STRING,
1320
+ default: 'fooVal',
1321
+ },
1322
+ bar: {
1323
+ type: DataType.STRING,
1324
+ default: 'barVal',
1325
+ },
1326
+ },
1327
+ });
1328
+ const adapter = new MemoryAdapter(schema.container, {});
1329
+ const input1 = {foo: 'a1', bar: 'a2'};
1330
+ const input2 = {foo: 'b1', bar: 'b2'};
1331
+ const input3 = {foo: 'c1', bar: 'c2'};
1332
+ const created1 = await adapter.create('model', input1);
1333
+ const created2 = await adapter.create('model', input2);
1334
+ const created3 = await adapter.create('model', input3);
1335
+ const id1 = created1[DEF_PK];
1336
+ const id2 = created2[DEF_PK];
1337
+ const id3 = created3[DEF_PK];
1338
+ const table = adapter._getTableOrCreate('model');
1339
+ const createdItems = Array.from(table.values());
1340
+ expect(createdItems).to.be.eql([
1341
+ {[DEF_PK]: id1, ...input1},
1342
+ {[DEF_PK]: id2, ...input2},
1343
+ {[DEF_PK]: id3, ...input3},
1344
+ ]);
1345
+ const result = await adapter.patch('model', {foo: undefined});
1346
+ expect(result).to.be.eq(3);
1347
+ const patchedItems = Array.from(table.values());
1348
+ expect(patchedItems).to.be.eql([
1349
+ {[DEF_PK]: id1, foo: 'fooVal', bar: 'a2'},
1350
+ {[DEF_PK]: id2, foo: 'fooVal', bar: 'b2'},
1351
+ {[DEF_PK]: id3, foo: 'fooVal', bar: 'c2'},
1352
+ ]);
1353
+ });
1354
+
1355
+ it('sets a default values for patched properties with a null value', async function () {
1356
+ const schema = new Schema();
1357
+ schema.defineDatasource({
1358
+ name: 'memory',
1359
+ adapter: 'memory',
1360
+ });
1361
+ schema.defineModel({
1362
+ name: 'model',
1363
+ datasource: 'memory',
1364
+ properties: {
1365
+ foo: {
1366
+ type: DataType.STRING,
1367
+ default: 'fooVal',
1368
+ },
1369
+ bar: {
1370
+ type: DataType.STRING,
1371
+ default: 'barVal',
1372
+ },
1373
+ },
1374
+ });
1375
+ const adapter = new MemoryAdapter(schema.container, {});
1376
+ const input1 = {foo: 'a1', bar: 'a2'};
1377
+ const input2 = {foo: 'b1', bar: 'b2'};
1378
+ const input3 = {foo: 'c1', bar: 'c2'};
1379
+ const created1 = await adapter.create('model', input1);
1380
+ const created2 = await adapter.create('model', input2);
1381
+ const created3 = await adapter.create('model', input3);
1382
+ const id1 = created1[DEF_PK];
1383
+ const id2 = created2[DEF_PK];
1384
+ const id3 = created3[DEF_PK];
1385
+ const table = adapter._getTableOrCreate('model');
1386
+ const createdItems = Array.from(table.values());
1387
+ expect(createdItems).to.be.eql([
1388
+ {[DEF_PK]: id1, ...input1},
1389
+ {[DEF_PK]: id2, ...input2},
1390
+ {[DEF_PK]: id3, ...input3},
1391
+ ]);
1392
+ const result = await adapter.patch('model', {foo: null});
1393
+ expect(result).to.be.eq(3);
1394
+ const patchedItems = Array.from(table.values());
1395
+ expect(patchedItems).to.be.eql([
1396
+ {[DEF_PK]: id1, foo: 'fooVal', bar: 'a2'},
1397
+ {[DEF_PK]: id2, foo: 'fooVal', bar: 'b2'},
1398
+ {[DEF_PK]: id3, foo: 'fooVal', bar: 'c2'},
1399
+ ]);
1400
+ });
1401
+
1402
+ it('uses a specified column name for a regular property', async function () {
1403
+ const schema = new Schema();
1404
+ schema.defineDatasource({
1405
+ name: 'memory',
1406
+ adapter: 'memory',
1407
+ });
1408
+ schema.defineModel({
1409
+ name: 'model',
1410
+ datasource: 'memory',
1411
+ properties: {
1412
+ foo: {
1413
+ type: DataType.STRING,
1414
+ columnName: 'fooCol',
1415
+ },
1416
+ bar: {
1417
+ type: DataType.STRING,
1418
+ columnName: 'barCol',
1419
+ },
1420
+ },
1421
+ });
1422
+ const adapter = new MemoryAdapter(schema.container, {});
1423
+ const input1 = {foo: 'a1', bar: 'a2'};
1424
+ const input2 = {foo: 'b1', bar: 'b2'};
1425
+ const input3 = {foo: 'c1', bar: 'c2'};
1426
+ const created1 = await adapter.create('model', input1);
1427
+ const created2 = await adapter.create('model', input2);
1428
+ const created3 = await adapter.create('model', input3);
1429
+ const id1 = created1[DEF_PK];
1430
+ const id2 = created2[DEF_PK];
1431
+ const id3 = created3[DEF_PK];
1432
+ const table = adapter._getTableOrCreate('model');
1433
+ const createdItems = Array.from(table.values());
1434
+ expect(createdItems).to.be.eql([
1435
+ {[DEF_PK]: id1, fooCol: 'a1', barCol: 'a2'},
1436
+ {[DEF_PK]: id2, fooCol: 'b1', barCol: 'b2'},
1437
+ {[DEF_PK]: id3, fooCol: 'c1', barCol: 'c2'},
1438
+ ]);
1439
+ const result = await adapter.patch('model', {foo: 'd1'});
1440
+ expect(result).to.be.eq(3);
1441
+ const patchedItems = Array.from(table.values());
1442
+ expect(patchedItems).to.be.eql([
1443
+ {[DEF_PK]: id1, fooCol: 'd1', barCol: 'a2'},
1444
+ {[DEF_PK]: id2, fooCol: 'd1', barCol: 'b2'},
1445
+ {[DEF_PK]: id3, fooCol: 'd1', barCol: 'c2'},
1446
+ ]);
1447
+ });
1448
+
1449
+ it('uses a specified column name for a regular property with a default value', async function () {
1450
+ const schema = new Schema();
1451
+ schema.defineDatasource({
1452
+ name: 'memory',
1453
+ adapter: 'memory',
1454
+ });
1455
+ schema.defineModel({
1456
+ name: 'model',
1457
+ datasource: 'memory',
1458
+ properties: {
1459
+ foo: {
1460
+ type: DataType.STRING,
1461
+ columnName: 'fooCol',
1462
+ default: 'fooVal',
1463
+ },
1464
+ bar: {
1465
+ type: DataType.STRING,
1466
+ columnName: 'barCol',
1467
+ default: 'barVal',
1468
+ },
1469
+ },
1470
+ });
1471
+ const adapter = new MemoryAdapter(schema.container, {});
1472
+ const input1 = {foo: 'a1', bar: 'a2'};
1473
+ const input2 = {foo: 'b1', bar: 'b2'};
1474
+ const input3 = {foo: 'c1', bar: 'c2'};
1475
+ const created1 = await adapter.create('model', input1);
1476
+ const created2 = await adapter.create('model', input2);
1477
+ const created3 = await adapter.create('model', input3);
1478
+ const id1 = created1[DEF_PK];
1479
+ const id2 = created2[DEF_PK];
1480
+ const id3 = created3[DEF_PK];
1481
+ const table = adapter._getTableOrCreate('model');
1482
+ const createdItems = Array.from(table.values());
1483
+ expect(createdItems).to.be.eql([
1484
+ {[DEF_PK]: id1, fooCol: 'a1', barCol: 'a2'},
1485
+ {[DEF_PK]: id2, fooCol: 'b1', barCol: 'b2'},
1486
+ {[DEF_PK]: id3, fooCol: 'c1', barCol: 'c2'},
1487
+ ]);
1488
+ const result = await adapter.patch('model', {foo: undefined});
1489
+ expect(result).to.be.eq(3);
1490
+ const patchedItems = Array.from(table.values());
1491
+ expect(patchedItems).to.be.eql([
1492
+ {[DEF_PK]: id1, fooCol: 'fooVal', barCol: 'a2'},
1493
+ {[DEF_PK]: id2, fooCol: 'fooVal', barCol: 'b2'},
1494
+ {[DEF_PK]: id3, fooCol: 'fooVal', barCol: 'c2'},
1495
+ ]);
1496
+ });
1497
+
1498
+ it('returns zero if nothing matched by the "where" clause', async function () {
1499
+ const schema = new Schema();
1500
+ schema.defineDatasource({
1501
+ name: 'memory',
1502
+ adapter: 'memory',
1503
+ });
1504
+ schema.defineModel({
1505
+ name: 'model',
1506
+ datasource: 'memory',
1507
+ properties: {
1508
+ foo: DataType.STRING,
1509
+ bar: DataType.STRING,
1510
+ },
1511
+ });
1512
+ const adapter = new MemoryAdapter(schema.container, {});
1513
+ const input1 = {foo: 'a1', bar: 'a2'};
1514
+ const input2 = {foo: 'b1', bar: 'b2'};
1515
+ const input3 = {foo: 'c1', bar: 'c2'};
1516
+ const created1 = await adapter.create('model', input1);
1517
+ const created2 = await adapter.create('model', input2);
1518
+ const created3 = await adapter.create('model', input3);
1519
+ const id1 = created1[DEF_PK];
1520
+ const id2 = created2[DEF_PK];
1521
+ const id3 = created3[DEF_PK];
1522
+ const table = adapter._getTableOrCreate('model');
1523
+ const createdItems = Array.from(table.values());
1524
+ expect(createdItems).to.be.eql([
1525
+ {[DEF_PK]: id1, ...input1},
1526
+ {[DEF_PK]: id2, ...input2},
1527
+ {[DEF_PK]: id3, ...input3},
1528
+ ]);
1529
+ const result = await adapter.patch('model', {foo: 'test'}, {baz: 'd3'});
1530
+ expect(result).to.be.eq(0);
1531
+ const patchedItems = Array.from(table.values());
1532
+ expect(patchedItems).to.be.eql([
1533
+ {[DEF_PK]: id1, foo: 'a1', bar: 'a2'},
1534
+ {[DEF_PK]: id2, foo: 'b1', bar: 'b2'},
1535
+ {[DEF_PK]: id3, foo: 'c1', bar: 'c2'},
1536
+ ]);
1537
+ });
1538
+
1539
+ it('uses the "where" clause to patch specific items', async function () {
1540
+ const schema = new Schema();
1541
+ schema.defineDatasource({
1542
+ name: 'memory',
1543
+ adapter: 'memory',
1544
+ });
1545
+ schema.defineModel({
1546
+ name: 'model',
1547
+ datasource: 'memory',
1548
+ properties: {
1549
+ foo: DataType.STRING,
1550
+ bar: DataType.STRING,
1551
+ },
1552
+ });
1553
+ const adapter = new MemoryAdapter(schema.container, {});
1554
+ const input1 = {foo: 'a', bar: '1'};
1555
+ const input2 = {foo: 'b', bar: '2'};
1556
+ const input3 = {foo: 'c', bar: '2'};
1557
+ const created1 = await adapter.create('model', input1);
1558
+ const created2 = await adapter.create('model', input2);
1559
+ const created3 = await adapter.create('model', input3);
1560
+ const id1 = created1[DEF_PK];
1561
+ const id2 = created2[DEF_PK];
1562
+ const id3 = created3[DEF_PK];
1563
+ const table = adapter._getTableOrCreate('model');
1564
+ const createdItems = Array.from(table.values());
1565
+ expect(createdItems).to.be.eql([
1566
+ {[DEF_PK]: id1, ...input1},
1567
+ {[DEF_PK]: id2, ...input2},
1568
+ {[DEF_PK]: id3, ...input3},
1569
+ ]);
1570
+ const result = await adapter.patch('model', {foo: 'd'}, {bar: '2'});
1571
+ expect(result).to.be.eq(2);
1572
+ const patchedItems = Array.from(table.values());
1573
+ expect(patchedItems).to.be.eql([
1574
+ {[DEF_PK]: id1, foo: 'a', bar: '1'},
1575
+ {[DEF_PK]: id2, foo: 'd', bar: '2'},
1576
+ {[DEF_PK]: id3, foo: 'd', bar: '2'},
1577
+ ]);
1578
+ });
1579
+
1580
+ it('the "where" clause uses property names instead of column names', async function () {
1581
+ const schema = new Schema();
1582
+ schema.defineDatasource({
1583
+ name: 'memory',
1584
+ adapter: 'memory',
1585
+ });
1586
+ schema.defineModel({
1587
+ name: 'model',
1588
+ datasource: 'memory',
1589
+ properties: {
1590
+ foo: {
1591
+ type: DataType.STRING,
1592
+ columnName: 'fooVal',
1593
+ },
1594
+ bar: {
1595
+ type: DataType.STRING,
1596
+ columnName: 'barVal',
1597
+ },
1598
+ },
1599
+ });
1600
+ const adapter = new MemoryAdapter(schema.container, {});
1601
+ const input1 = {foo: 'a', bar: '1'};
1602
+ const input2 = {foo: 'b', bar: '2'};
1603
+ const input3 = {foo: 'c', bar: '2'};
1604
+ const created1 = await adapter.create('model', input1);
1605
+ const created2 = await adapter.create('model', input2);
1606
+ const created3 = await adapter.create('model', input3);
1607
+ const id1 = created1[DEF_PK];
1608
+ const id2 = created2[DEF_PK];
1609
+ const id3 = created3[DEF_PK];
1610
+ const table = adapter._getTableOrCreate('model');
1611
+ const createdItems = Array.from(table.values());
1612
+ expect(createdItems).to.be.eql([
1613
+ {[DEF_PK]: id1, fooVal: 'a', barVal: '1'},
1614
+ {[DEF_PK]: id2, fooVal: 'b', barVal: '2'},
1615
+ {[DEF_PK]: id3, fooVal: 'c', barVal: '2'},
1616
+ ]);
1617
+ const result = await adapter.patch('model', {foo: 'd'}, {bar: '2'});
1618
+ expect(result).to.be.eq(2);
1619
+ const patchedItems = Array.from(table.values());
1620
+ expect(patchedItems).to.be.eql([
1621
+ {[DEF_PK]: id1, fooVal: 'a', barVal: '1'},
1622
+ {[DEF_PK]: id2, fooVal: 'd', barVal: '2'},
1623
+ {[DEF_PK]: id3, fooVal: 'd', barVal: '2'},
1624
+ ]);
1625
+ });
1626
+
1627
+ it('the "where" clause uses a persisted data instead of default values in case of undefined', async function () {
1628
+ const schema = new Schema();
1629
+ schema.defineDatasource({
1630
+ name: 'memory',
1631
+ adapter: 'memory',
1632
+ });
1633
+ schema.defineModel({
1634
+ name: 'model',
1635
+ datasource: 'memory',
1636
+ properties: {
1637
+ foo: DataType.STRING,
1638
+ bar: {
1639
+ type: DataType.STRING,
1640
+ default: 'barVal',
1641
+ },
1642
+ },
1643
+ });
1644
+ const adapter = new MemoryAdapter(schema.container, {});
1645
+ const input1 = {[DEF_PK]: 1, foo: 'a', bar: undefined};
1646
+ const input2 = {[DEF_PK]: 2, foo: 'b', bar: undefined};
1647
+ const input3 = {[DEF_PK]: 3, foo: 'c', bar: 10};
1648
+ const input4 = {[DEF_PK]: 4, foo: 'd', bar: null};
1649
+ const table = adapter._getTableOrCreate('model');
1650
+ table.set(input1[DEF_PK], input1);
1651
+ table.set(input2[DEF_PK], input2);
1652
+ table.set(input3[DEF_PK], input3);
1653
+ table.set(input4[DEF_PK], input4);
1654
+ const result = await adapter.patch('model', {foo: 'e'}, {bar: undefined});
1655
+ expect(result).to.be.eq(2);
1656
+ const patchedItems = Array.from(table.values());
1657
+ expect(patchedItems).to.be.eql([
1658
+ {[DEF_PK]: 1, foo: 'e', bar: undefined},
1659
+ {[DEF_PK]: 2, foo: 'e', bar: undefined},
1660
+ {[DEF_PK]: 3, foo: 'c', bar: 10},
1661
+ {[DEF_PK]: 4, foo: 'd', bar: null},
1662
+ ]);
1663
+ });
1664
+
1665
+ it('the "where" clause uses a persisted data instead of default values in case of null', async function () {
1666
+ const schema = new Schema();
1667
+ schema.defineDatasource({
1668
+ name: 'memory',
1669
+ adapter: 'memory',
1670
+ });
1671
+ schema.defineModel({
1672
+ name: 'model',
1673
+ datasource: 'memory',
1674
+ properties: {
1675
+ foo: DataType.STRING,
1676
+ bar: {
1677
+ type: DataType.STRING,
1678
+ default: 'barVal',
1679
+ },
1680
+ },
1681
+ });
1682
+ const adapter = new MemoryAdapter(schema.container, {});
1683
+ const input1 = {[DEF_PK]: 1, foo: 'a', bar: undefined};
1684
+ const input2 = {[DEF_PK]: 2, foo: 'b', bar: undefined};
1685
+ const input3 = {[DEF_PK]: 3, foo: 'c', bar: 10};
1686
+ const input4 = {[DEF_PK]: 4, foo: 'd', bar: null};
1687
+ const table = adapter._getTableOrCreate('model');
1688
+ table.set(input1[DEF_PK], input1);
1689
+ table.set(input2[DEF_PK], input2);
1690
+ table.set(input3[DEF_PK], input3);
1691
+ table.set(input4[DEF_PK], input4);
1692
+ const result = await adapter.patch('model', {foo: 'e'}, {bar: null});
1693
+ expect(result).to.be.eq(1);
1694
+ const patchedItems = Array.from(table.values());
1695
+ expect(patchedItems).to.be.eql([
1696
+ {[DEF_PK]: 1, foo: 'a', bar: undefined},
1697
+ {[DEF_PK]: 2, foo: 'b', bar: undefined},
1698
+ {[DEF_PK]: 3, foo: 'c', bar: 10},
1699
+ {[DEF_PK]: 4, foo: 'e', bar: null},
1700
+ ]);
1701
+ });
1702
+ });
1703
+
1136
1704
  describe('patchById', function () {
1137
1705
  it('updates only provided properties by a given identifier', async function () {
1138
1706
  const schema = new Schema();
@@ -2050,7 +2618,7 @@ describe('MemoryAdapter', function () {
2050
2618
  expect(result4[2]).to.be.eql({[DEF_PK]: 2, foo: 2, bar: 10});
2051
2619
  });
2052
2620
 
2053
- it('allows to specify a where clause to filter a return value', async function () {
2621
+ it('allows to specify the "where" clause to filter a return value', async function () {
2054
2622
  const schema = new Schema();
2055
2623
  schema.defineDatasource({
2056
2624
  name: 'memory',
@@ -2087,7 +2655,7 @@ describe('MemoryAdapter', function () {
2087
2655
  expect(result3[1]).to.be.eql({[DEF_PK]: 2, ...input2});
2088
2656
  });
2089
2657
 
2090
- it('a where clause uses property names instead of column names', async function () {
2658
+ it('the "where" clause uses property names instead of column names', async function () {
2091
2659
  const schema = new Schema();
2092
2660
  schema.defineDatasource({
2093
2661
  name: 'memory',
@@ -2144,7 +2712,7 @@ describe('MemoryAdapter', function () {
2144
2712
  expect(result3[1]).to.be.eql({[DEF_PK]: 2, ...input2});
2145
2713
  });
2146
2714
 
2147
- it('a where clause uses a persisted data instead of default values', async function () {
2715
+ it('the "where" clause uses a persisted data instead of default values', async function () {
2148
2716
  const schema = new Schema();
2149
2717
  schema.defineDatasource({
2150
2718
  name: 'memory',
@@ -2585,7 +3153,7 @@ describe('MemoryAdapter', function () {
2585
3153
  expect(table.get(2)).to.be.eql({[DEF_PK]: 2, foo: 10});
2586
3154
  });
2587
3155
 
2588
- it('a where clause uses property names instead of column names', async function () {
3156
+ it('the "where" clause uses property names instead of column names', async function () {
2589
3157
  const schema = new Schema();
2590
3158
  schema.defineDatasource({
2591
3159
  name: 'memory',
@@ -2612,7 +3180,7 @@ describe('MemoryAdapter', function () {
2612
3180
  expect(table.get(2)).to.be.eql({[DEF_PK]: 2, fooCol: 10});
2613
3181
  });
2614
3182
 
2615
- it('a where clause uses a persisted data instead of default values', async function () {
3183
+ it('the "where" clause uses a persisted data instead of default values', async function () {
2616
3184
  const schema = new Schema();
2617
3185
  schema.defineDatasource({
2618
3186
  name: 'memory',
@@ -2863,7 +3431,7 @@ describe('MemoryAdapter', function () {
2863
3431
  expect(table.get(3)).to.be.eql({[DEF_PK]: 3, foo: 15});
2864
3432
  });
2865
3433
 
2866
- it('a where clause uses property names instead of column names', async function () {
3434
+ it('the "where" clause uses property names instead of column names', async function () {
2867
3435
  const schema = new Schema();
2868
3436
  schema.defineDatasource({
2869
3437
  name: 'memory',
@@ -2893,7 +3461,7 @@ describe('MemoryAdapter', function () {
2893
3461
  expect(table.get(3)).to.be.eql({[DEF_PK]: 3, fooCol: 15});
2894
3462
  });
2895
3463
 
2896
- it('a where clause uses a persisted data instead of default values', async function () {
3464
+ it('the "where" clause uses a persisted data instead of default values', async function () {
2897
3465
  const schema = new Schema();
2898
3466
  schema.defineDatasource({
2899
3467
  name: 'memory',
@@ -35,6 +35,12 @@ export class DataSanitizingDecorator extends Service {
35
35
  return replaceById.call(this, modelName, id, modelData, filter);
36
36
  };
37
37
 
38
+ const patch = adapter.patch;
39
+ adapter.patch = async function (modelName, modelData, where) {
40
+ modelData = sanitize(modelName, modelData);
41
+ return patch.call(this, modelName, modelData, where);
42
+ };
43
+
38
44
  const patchById = adapter.patchById;
39
45
  adapter.patchById = async function (modelName, id, modelData, filter) {
40
46
  modelData = sanitize(modelName, modelData);
@@ -18,6 +18,11 @@ class TestAdapter extends Adapter {
18
18
  return Promise.resolve({});
19
19
  }
20
20
 
21
+ // eslint-disable-next-line no-unused-vars
22
+ patch(modelName, modelData, where = undefined) {
23
+ return Promise.resolve(1);
24
+ }
25
+
21
26
  // eslint-disable-next-line no-unused-vars
22
27
  patchById(modelName, id, modelData, filter = undefined) {
23
28
  return Promise.resolve({});
@@ -49,6 +54,14 @@ describe('DataSanitizingDecorator', function () {
49
54
  expect(V.sanitize).to.be.called.with.exactly('model', data);
50
55
  });
51
56
 
57
+ it('overrides the "patch" method and sanitizes a given data', async function () {
58
+ sandbox.on(V, 'sanitize');
59
+ const data = {};
60
+ await A.patch('model', data);
61
+ expect(V.sanitize).to.be.called.once;
62
+ expect(V.sanitize).to.be.called.with.exactly('model', data);
63
+ });
64
+
52
65
  it('overrides the "patchById" method and sanitizes a given data', async function () {
53
66
  sandbox.on(V, 'sanitize');
54
67
  const data = {};
@@ -32,6 +32,12 @@ export class DataValidationDecorator extends Service {
32
32
  return replaceById.call(this, modelName, id, modelData, filter);
33
33
  };
34
34
 
35
+ const patch = adapter.patch;
36
+ adapter.patch = function (modelName, modelData, where) {
37
+ this.getService(ModelDataValidator).validate(modelName, modelData, true);
38
+ return patch.call(this, modelName, modelData, where);
39
+ };
40
+
35
41
  const patchById = adapter.patchById;
36
42
  adapter.patchById = function (modelName, id, modelData, filter) {
37
43
  this.getService(ModelDataValidator).validate(modelName, modelData, true);
@@ -18,6 +18,11 @@ class TestAdapter extends Adapter {
18
18
  return Promise.resolve({});
19
19
  }
20
20
 
21
+ // eslint-disable-next-line no-unused-vars
22
+ patch(modelName, modelData, where = undefined) {
23
+ return Promise.resolve(1);
24
+ }
25
+
21
26
  // eslint-disable-next-line no-unused-vars
22
27
  patchById(modelName, id, modelData, filter = undefined) {
23
28
  return Promise.resolve({});
@@ -36,6 +36,12 @@ export class DefaultValuesDecorator extends Service {
36
36
  return replaceById.call(this, modelName, id, modelData, filter);
37
37
  };
38
38
 
39
+ const patch = adapter.patch;
40
+ adapter.patch = function (modelName, modelData, where) {
41
+ modelData = setDefaults(modelName, modelData, true);
42
+ return patch.call(this, modelName, modelData, where);
43
+ };
44
+
39
45
  const patchById = adapter.patchById;
40
46
  adapter.patchById = function (modelName, id, modelData, filter) {
41
47
  modelData = setDefaults(modelName, modelData, true);
@@ -29,6 +29,11 @@ class TestAdapter extends Adapter {
29
29
  return modelData;
30
30
  }
31
31
 
32
+ // eslint-disable-next-line no-unused-vars
33
+ patch(modelName, modelData, where = undefined) {
34
+ return Promise.resolve(modelData);
35
+ }
36
+
32
37
  // eslint-disable-next-line no-unused-vars
33
38
  async patchById(modelName, id, modelData, filter = undefined) {
34
39
  return modelData;
@@ -76,6 +81,47 @@ describe('DefaultValuesDecorator', function () {
76
81
  );
77
82
  });
78
83
 
84
+ describe('overrides the "patch" method and sets default values to input data', function () {
85
+ it('does not set default values to not existing properties of input data', async function () {
86
+ sandbox.on(U, 'setDefaultValuesToEmptyProperties');
87
+ const data = {};
88
+ const retval = await A.patch('model', data);
89
+ expect(retval).to.be.eql({});
90
+ expect(U.setDefaultValuesToEmptyProperties).to.be.called.once;
91
+ expect(U.setDefaultValuesToEmptyProperties).to.be.called.with.exactly(
92
+ 'model',
93
+ data,
94
+ true,
95
+ );
96
+ });
97
+
98
+ it('does set default values to input properties of null', async function () {
99
+ sandbox.on(U, 'setDefaultValuesToEmptyProperties');
100
+ const data = {prop: null};
101
+ const retval = await A.patch('model', data);
102
+ expect(retval).to.be.eql({prop: 'value'});
103
+ expect(U.setDefaultValuesToEmptyProperties).to.be.called.once;
104
+ expect(U.setDefaultValuesToEmptyProperties).to.be.called.with.exactly(
105
+ 'model',
106
+ data,
107
+ true,
108
+ );
109
+ });
110
+
111
+ it('does set default values to input properties of undefined', async function () {
112
+ sandbox.on(U, 'setDefaultValuesToEmptyProperties');
113
+ const data = {prop: undefined};
114
+ const retval = await A.patch('model', data);
115
+ expect(retval).to.be.eql({prop: 'value'});
116
+ expect(U.setDefaultValuesToEmptyProperties).to.be.called.once;
117
+ expect(U.setDefaultValuesToEmptyProperties).to.be.called.with.exactly(
118
+ 'model',
119
+ data,
120
+ true,
121
+ );
122
+ });
123
+ });
124
+
79
125
  describe('overrides the "patchById" method and sets default values to input data', function () {
80
126
  it('does not set default values to not existing properties of input data', async function () {
81
127
  sandbox.on(U, 'setDefaultValuesToEmptyProperties');
@@ -43,9 +43,9 @@ export declare type ItemFilterClause = Pick<FilterClause, 'fields' | 'include'>;
43
43
  * ```
44
44
  */
45
45
  export declare type WhereClause =
46
- & PropertiesClause
47
- & AndClause
48
- & OrClause;
46
+ | AndClause
47
+ | OrClause
48
+ | PropertiesClause;
49
49
 
50
50
  /**
51
51
  * Properties clause.
@@ -127,8 +127,12 @@ export class WhereClauseTool extends Service {
127
127
  */
128
128
  _test(example, value) {
129
129
  // Test null.
130
- if (example == null) {
131
- return value == null;
130
+ if (example === null) {
131
+ return value === null;
132
+ }
133
+ // Test undefined.
134
+ if (example === undefined) {
135
+ return value === undefined;
132
136
  }
133
137
  // Test RegExp.
134
138
  // noinspection ALL
@@ -266,11 +266,16 @@ describe('WhereClauseTool', function () {
266
266
  expect(result[0]).to.be.eql(OBJECTS[2]);
267
267
  });
268
268
 
269
- it('uses null to match an undefined and null value', function () {
269
+ it('does not use null to match an undefined value', function () {
270
270
  const result = S.filter(OBJECTS, {nickname: null});
271
- expect(result).to.have.length(2);
271
+ expect(result).to.have.length(1);
272
272
  expect(result[0]).to.be.eql(OBJECTS[2]);
273
- expect(result[1]).to.be.eql(OBJECTS[3]);
273
+ });
274
+
275
+ it('does not use undefined to match a null value', function () {
276
+ const result = S.filter(OBJECTS, {nickname: undefined});
277
+ expect(result).to.have.length(1);
278
+ expect(result[0]).to.be.eql(OBJECTS[3]);
274
279
  });
275
280
  });
276
281
 
@@ -77,6 +77,17 @@ export declare class Repository<
77
77
  filter?: ItemFilterClause,
78
78
  ): Promise<FlatData>;
79
79
 
80
+ /**
81
+ * Patch.
82
+ *
83
+ * @param data
84
+ * @param where
85
+ */
86
+ patch(
87
+ data: PartialWithoutId<IdName, Data>,
88
+ where?: WhereClause,
89
+ ): Promise<number>;
90
+
80
91
  /**
81
92
  * Patch by id.
82
93
  *
@@ -111,6 +111,18 @@ export class Repository extends Service {
111
111
  return this.replaceById(pkValue, data, filter);
112
112
  }
113
113
 
114
+ /**
115
+ * Patch.
116
+ *
117
+ * @param {object} data
118
+ * @param {object|undefined} where
119
+ * @returns {Promise<number>}
120
+ */
121
+ async patch(data, where = undefined) {
122
+ const adapter = await this.getAdapter();
123
+ return adapter.patch(this.modelName, data, where);
124
+ }
125
+
114
126
  /**
115
127
  * Patch by id.
116
128
  *
@@ -50,6 +50,32 @@ describe('Repository', function () {
50
50
  });
51
51
  });
52
52
 
53
+ describe('patch', function () {
54
+ it('patches all items', async function () {
55
+ const schema = new Schema();
56
+ schema.defineDatasource({name: 'datasource', adapter: 'memory'});
57
+ schema.defineModel({name: 'model', datasource: 'datasource'});
58
+ const rep = schema.getRepository('model');
59
+ await rep.create({foo: 'a1', bar: 'b1'});
60
+ await rep.create({foo: 'a2', bar: 'b2'});
61
+ await rep.create({foo: 'a3', bar: 'b3'});
62
+ const result = await rep.patch({foo: 'test'});
63
+ expect(result).to.be.eq(3);
64
+ });
65
+
66
+ it('patches found items by the "where" clause', async function () {
67
+ const schema = new Schema();
68
+ schema.defineDatasource({name: 'datasource', adapter: 'memory'});
69
+ schema.defineModel({name: 'model', datasource: 'datasource'});
70
+ const rep = schema.getRepository('model');
71
+ await rep.create({foo: 'a', bar: '1'});
72
+ await rep.create({foo: 'b', bar: '2'});
73
+ await rep.create({foo: 'c', bar: '2'});
74
+ const result = await rep.patch({foo: 'test'}, {bar: '2'});
75
+ expect(result).to.be.eq(2);
76
+ });
77
+ });
78
+
53
79
  describe('patchById', function () {
54
80
  it('patches an item by the given id', async function () {
55
81
  const schema = new Schema();