@e22m4u/js-repository-mongodb-adapter 0.0.18 → 0.0.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/js-repository-mongodb-adapter",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "MongoDB адаптер для @e22m4u/js-repository",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -38,7 +38,7 @@
38
38
  "peerDependencies": {
39
39
  "@e22m4u/js-format": "*",
40
40
  "@e22m4u/js-service": "*",
41
- "@e22m4u/js-repository": "~0.0.33"
41
+ "@e22m4u/js-repository": "~0.0.34"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@commitlint/cli": "^17.7.1",
@@ -267,14 +267,15 @@ export class MongodbAdapter extends Adapter {
267
267
  * @private
268
268
  */
269
269
  _buildProjection(modelName, fields) {
270
- if (!fields) return;
271
- fields = Array.isArray(fields) ? fields : [fields];
270
+ if (fields == null) return;
271
+ if (Array.isArray(fields) === false) fields = [fields];
272
272
  if (!fields.length) return;
273
273
  if (fields.indexOf('_id') === -1) fields.push('_id');
274
274
  return fields.reduce((acc, field) => {
275
275
  if (!field || typeof field !== 'string')
276
276
  throw new InvalidArgumentError(
277
- 'A field name must be a non-empty String, but %v given.',
277
+ 'The provided option "fields" should be a non-empty String ' +
278
+ 'or an Array of non-empty String, but %v given.',
278
279
  field,
279
280
  );
280
281
  let colName = this._getColName(modelName, field);
@@ -321,24 +322,25 @@ export class MongodbAdapter extends Adapter {
321
322
  * @private
322
323
  */
323
324
  _buildSort(modelName, clause) {
324
- if (!clause) return;
325
- clause = Array.isArray(clause) ? clause : [clause];
325
+ if (clause == null) return;
326
+ if (Array.isArray(clause) === false) clause = [clause];
326
327
  if (!clause.length) return;
327
328
  const utils = this.getService(ModelDefinitionUtils);
328
329
  const idPropName = this._getIdPropName(modelName);
329
330
  return clause.reduce((acc, order) => {
330
331
  if (!order || typeof order !== 'string')
331
332
  throw new InvalidArgumentError(
332
- 'A field order must be a non-empty String, but %v given.',
333
+ 'The provided option "order" should be a non-empty String ' +
334
+ 'or an Array of non-empty String, but %v given.',
333
335
  order,
334
336
  );
335
337
  const direction = order.match(/\s+(A|DE)SC$/);
336
- let key = order.replace(/\s+(A|DE)SC$/, '').trim();
337
- if (key === idPropName) {
338
- key = '_id';
338
+ let field = order.replace(/\s+(A|DE)SC$/, '').trim();
339
+ if (field === idPropName) {
340
+ field = '_id';
339
341
  } else {
340
342
  try {
341
- key = utils.getColumnNameByPropertyName(modelName, key);
343
+ field = utils.getColumnNameByPropertyName(modelName, field);
342
344
  } catch (error) {
343
345
  if (
344
346
  !(error instanceof InvalidArgumentError) ||
@@ -348,7 +350,7 @@ export class MongodbAdapter extends Adapter {
348
350
  }
349
351
  }
350
352
  }
351
- acc[key] = direction && direction[1] === 'DE' ? -1 : 1;
353
+ acc[field] = direction && direction[1] === 'DE' ? -1 : 1;
352
354
  return acc;
353
355
  }, {});
354
356
  }
@@ -577,8 +579,8 @@ export class MongodbAdapter extends Adapter {
577
579
  modelData[idPropName] = id;
578
580
  const tableData = this._toDatabase(modelName, modelData);
579
581
  const table = this._getCollection(modelName);
580
- const {modifiedCount} = await table.replaceOne({_id: id}, tableData);
581
- if (modifiedCount < 1)
582
+ const {matchedCount} = await table.replaceOne({_id: id}, tableData);
583
+ if (matchedCount < 1)
582
584
  throw new InvalidArgumentError('Identifier %v is not found.', String(id));
583
585
  const projection = this._buildProjection(
584
586
  modelName,
@@ -603,8 +605,8 @@ export class MongodbAdapter extends Adapter {
603
605
  delete modelData[idPropName];
604
606
  const tableData = this._toDatabase(modelName, modelData);
605
607
  const table = this._getCollection(modelName);
606
- const {modifiedCount} = await table.updateOne({_id: id}, {$set: tableData});
607
- if (modifiedCount < 1)
608
+ const {matchedCount} = await table.updateOne({_id: id}, {$set: tableData});
609
+ if (matchedCount < 1)
608
610
  throw new InvalidArgumentError('Identifier %v is not found.', String(id));
609
611
  const projection = this._buildProjection(
610
612
  modelName,
@@ -41,6 +41,356 @@ describe('MongodbAdapter', function () {
41
41
  await MDB_CLIENT.close(true);
42
42
  });
43
43
 
44
+ describe('_buildProjection', function () {
45
+ describe('single field', function () {
46
+ it('returns undefined if the second argument is undefined', async function () {
47
+ const schema = createSchema();
48
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
49
+ const A = await schema
50
+ .getService(AdapterRegistry)
51
+ .getAdapter('mongodb');
52
+ const res = A._buildProjection('model', undefined);
53
+ expect(res).to.be.undefined;
54
+ });
55
+
56
+ it('returns undefined if the second argument is null', async function () {
57
+ const schema = createSchema();
58
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
59
+ const A = await schema
60
+ .getService(AdapterRegistry)
61
+ .getAdapter('mongodb');
62
+ const res = A._buildProjection('model', null);
63
+ expect(res).to.be.undefined;
64
+ });
65
+
66
+ it('requires the second argument to be a non-empty string', async function () {
67
+ const schema = createSchema();
68
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
69
+ const A = await schema
70
+ .getService(AdapterRegistry)
71
+ .getAdapter('mongodb');
72
+ const throwable = v => () => A._buildProjection('model', v);
73
+ const error = v =>
74
+ format(
75
+ 'The provided option "fields" should be a non-empty String ' +
76
+ 'or an Array of non-empty String, but %s given.',
77
+ v,
78
+ );
79
+ expect(throwable('')).to.throw(error('""'));
80
+ expect(throwable(10)).to.throw(error('10'));
81
+ expect(throwable(0)).to.throw(error('0'));
82
+ expect(throwable(true)).to.throw(error('true'));
83
+ expect(throwable(false)).to.throw(error('false'));
84
+ expect(throwable({})).to.throw(error('Object'));
85
+ expect(throwable('bar')()).to.be.eql({_id: 1, bar: 1});
86
+ expect(throwable(undefined)()).to.be.undefined;
87
+ expect(throwable(null)()).to.be.undefined;
88
+ });
89
+
90
+ it('converts the given property name to the column name', async function () {
91
+ const schema = createSchema();
92
+ schema.defineModel({
93
+ name: 'model',
94
+ datasource: 'mongodb',
95
+ properties: {
96
+ foo: {
97
+ type: DataType.STRING,
98
+ columnName: 'bar',
99
+ },
100
+ },
101
+ });
102
+ const A = await schema
103
+ .getService(AdapterRegistry)
104
+ .getAdapter('mongodb');
105
+ const res = A._buildProjection('model', 'foo');
106
+ expect(res).to.be.eql({_id: 1, bar: 1});
107
+ });
108
+
109
+ it('includes "_id" field to the projection', async function () {
110
+ const schema = createSchema();
111
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
112
+ const A = await schema
113
+ .getService(AdapterRegistry)
114
+ .getAdapter('mongodb');
115
+ const res = A._buildProjection('model', 'foo');
116
+ expect(res).to.be.eql({_id: 1, foo: 1});
117
+ });
118
+
119
+ it('includes "_id" as a column name of the given property', async function () {
120
+ const schema = createSchema();
121
+ schema.defineModel({
122
+ name: 'model',
123
+ datasource: 'mongodb',
124
+ properties: {
125
+ foo: {
126
+ type: DataType.STRING,
127
+ primaryKey: true,
128
+ columnName: '_id',
129
+ },
130
+ },
131
+ });
132
+ const A = await schema
133
+ .getService(AdapterRegistry)
134
+ .getAdapter('mongodb');
135
+ const res = A._buildProjection('model', 'foo');
136
+ expect(res).to.be.eql({_id: 1});
137
+ });
138
+ });
139
+
140
+ describe('multiple fields', function () {
141
+ it('returns undefined if the second argument is an empty array', async function () {
142
+ const schema = createSchema();
143
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
144
+ const A = await schema
145
+ .getService(AdapterRegistry)
146
+ .getAdapter('mongodb');
147
+ const res = A._buildProjection('model', []);
148
+ expect(res).to.be.undefined;
149
+ });
150
+
151
+ it('requires the second argument to be an array of non-empty strings', async function () {
152
+ const schema = createSchema();
153
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
154
+ const A = await schema
155
+ .getService(AdapterRegistry)
156
+ .getAdapter('mongodb');
157
+ const throwable = v => () => A._buildProjection('model', v);
158
+ const error = v =>
159
+ format(
160
+ 'The provided option "fields" should be a non-empty String ' +
161
+ 'or an Array of non-empty String, but %s given.',
162
+ v,
163
+ );
164
+ expect(throwable([''])).to.throw(error('""'));
165
+ expect(throwable([10])).to.throw(error('10'));
166
+ expect(throwable([0])).to.throw(error('0'));
167
+ expect(throwable([true])).to.throw(error('true'));
168
+ expect(throwable([false])).to.throw(error('false'));
169
+ expect(throwable([{}])).to.throw(error('Object'));
170
+ expect(throwable([undefined])).to.throw(error('undefined'));
171
+ expect(throwable([null])).to.throw(error('null'));
172
+ expect(throwable([])()).to.be.undefined;
173
+ expect(throwable(['bar'])()).to.be.eql({_id: 1, bar: 1});
174
+ });
175
+
176
+ it('converts the given property names to column names', async function () {
177
+ const schema = createSchema();
178
+ schema.defineModel({
179
+ name: 'model',
180
+ datasource: 'mongodb',
181
+ properties: {
182
+ foo: {
183
+ type: DataType.STRING,
184
+ columnName: 'bar',
185
+ },
186
+ baz: {
187
+ type: DataType.STRING,
188
+ columnName: 'qux',
189
+ },
190
+ },
191
+ });
192
+ const A = await schema
193
+ .getService(AdapterRegistry)
194
+ .getAdapter('mongodb');
195
+ const res = A._buildProjection('model', ['foo', 'baz']);
196
+ expect(res).to.be.eql({_id: 1, bar: 1, qux: 1});
197
+ });
198
+
199
+ it('includes "_id" field to the projection', async function () {
200
+ const schema = createSchema();
201
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
202
+ const A = await schema
203
+ .getService(AdapterRegistry)
204
+ .getAdapter('mongodb');
205
+ const res = A._buildProjection('model', ['foo', 'bar']);
206
+ expect(res).to.be.eql({_id: 1, foo: 1, bar: 1});
207
+ });
208
+
209
+ it('includes "_id" as a column name of the given property', async function () {
210
+ const schema = createSchema();
211
+ schema.defineModel({
212
+ name: 'model',
213
+ datasource: 'mongodb',
214
+ properties: {
215
+ foo: {
216
+ type: DataType.STRING,
217
+ primaryKey: true,
218
+ columnName: '_id',
219
+ },
220
+ },
221
+ });
222
+ const A = await schema
223
+ .getService(AdapterRegistry)
224
+ .getAdapter('mongodb');
225
+ const res = A._buildProjection('model', ['foo', 'bar']);
226
+ expect(res).to.be.eql({_id: 1, bar: 1});
227
+ });
228
+ });
229
+ });
230
+
231
+ describe('_buildSort', function () {
232
+ describe('single field', function () {
233
+ it('returns undefined if the second argument is undefined', async function () {
234
+ const schema = createSchema();
235
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
236
+ const A = await schema
237
+ .getService(AdapterRegistry)
238
+ .getAdapter('mongodb');
239
+ const res = A._buildSort('model', undefined);
240
+ expect(res).to.be.undefined;
241
+ });
242
+
243
+ it('returns undefined if the second argument is null', async function () {
244
+ const schema = createSchema();
245
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
246
+ const A = await schema
247
+ .getService(AdapterRegistry)
248
+ .getAdapter('mongodb');
249
+ const res = A._buildSort('model', null);
250
+ expect(res).to.be.undefined;
251
+ });
252
+
253
+ it('requires the second argument to be a non-empty string', async function () {
254
+ const schema = createSchema();
255
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
256
+ const A = await schema
257
+ .getService(AdapterRegistry)
258
+ .getAdapter('mongodb');
259
+ const throwable = v => () => A._buildSort('model', v);
260
+ const error = v =>
261
+ format(
262
+ 'The provided option "order" should be a non-empty String ' +
263
+ 'or an Array of non-empty String, but %s given.',
264
+ v,
265
+ );
266
+ expect(throwable('')).to.throw(error('""'));
267
+ expect(throwable(10)).to.throw(error('10'));
268
+ expect(throwable(0)).to.throw(error('0'));
269
+ expect(throwable(true)).to.throw(error('true'));
270
+ expect(throwable(false)).to.throw(error('false'));
271
+ expect(throwable({})).to.throw(error('Object'));
272
+ expect(throwable('bar')()).to.be.eql({bar: 1});
273
+ expect(throwable(undefined)()).to.be.undefined;
274
+ expect(throwable(null)()).to.be.undefined;
275
+ });
276
+
277
+ it('recognizes direction by the given direction flag', async function () {
278
+ const schema = createSchema();
279
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
280
+ const A = await schema
281
+ .getService(AdapterRegistry)
282
+ .getAdapter('mongodb');
283
+ const res1 = A._buildSort('model', 'foo');
284
+ const res2 = A._buildSort('model', 'foo DESC');
285
+ const res3 = A._buildSort('model', 'foo ASC');
286
+ expect(res1).to.be.eql({foo: 1});
287
+ expect(res2).to.be.eql({foo: -1});
288
+ expect(res3).to.be.eql({foo: 1});
289
+ });
290
+
291
+ it('converts the given property name to the column name', async function () {
292
+ const schema = createSchema();
293
+ schema.defineModel({
294
+ name: 'model',
295
+ datasource: 'mongodb',
296
+ properties: {
297
+ foo: {
298
+ type: DataType.STRING,
299
+ columnName: 'bar',
300
+ },
301
+ },
302
+ });
303
+ const A = await schema
304
+ .getService(AdapterRegistry)
305
+ .getAdapter('mongodb');
306
+ const res1 = A._buildSort('model', 'foo');
307
+ const res2 = A._buildSort('model', 'foo DESC');
308
+ const res3 = A._buildSort('model', 'foo ASC');
309
+ expect(res1).to.be.eql({bar: 1});
310
+ expect(res2).to.be.eql({bar: -1});
311
+ expect(res3).to.be.eql({bar: 1});
312
+ });
313
+ });
314
+
315
+ describe('multiple fields', function () {
316
+ it('returns undefined if the second argument is an empty array', async function () {
317
+ const schema = createSchema();
318
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
319
+ const A = await schema
320
+ .getService(AdapterRegistry)
321
+ .getAdapter('mongodb');
322
+ const res = A._buildSort('model', []);
323
+ expect(res).to.be.undefined;
324
+ });
325
+
326
+ it('requires the second argument to be an array of non-empty strings', async function () {
327
+ const schema = createSchema();
328
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
329
+ const A = await schema
330
+ .getService(AdapterRegistry)
331
+ .getAdapter('mongodb');
332
+ const throwable = v => () => A._buildSort('model', v);
333
+ const error = v =>
334
+ format(
335
+ 'The provided option "order" should be a non-empty String ' +
336
+ 'or an Array of non-empty String, but %s given.',
337
+ v,
338
+ );
339
+ expect(throwable([''])).to.throw(error('""'));
340
+ expect(throwable([10])).to.throw(error('10'));
341
+ expect(throwable([0])).to.throw(error('0'));
342
+ expect(throwable([true])).to.throw(error('true'));
343
+ expect(throwable([false])).to.throw(error('false'));
344
+ expect(throwable([{}])).to.throw(error('Object'));
345
+ expect(throwable([undefined])).to.throw(error('undefined'));
346
+ expect(throwable([null])).to.throw(error('null'));
347
+ expect(throwable([])()).to.be.undefined;
348
+ expect(throwable(['bar', 'baz'])()).to.be.eql({bar: 1, baz: 1});
349
+ });
350
+
351
+ it('recognizes direction by the given direction flag', async function () {
352
+ const schema = createSchema();
353
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
354
+ const A = await schema
355
+ .getService(AdapterRegistry)
356
+ .getAdapter('mongodb');
357
+ const res1 = A._buildSort('model', ['foo', 'bar']);
358
+ const res2 = A._buildSort('model', ['foo DESC', 'bar ASC']);
359
+ const res3 = A._buildSort('model', ['foo ASC', 'bar DESC']);
360
+ expect(res1).to.be.eql({foo: 1, bar: 1});
361
+ expect(res2).to.be.eql({foo: -1, bar: 1});
362
+ expect(res3).to.be.eql({foo: 1, bar: -1});
363
+ });
364
+
365
+ it('converts the given property names to column names', async function () {
366
+ const schema = createSchema();
367
+ schema.defineModel({
368
+ name: 'model',
369
+ datasource: 'mongodb',
370
+ properties: {
371
+ foo: {
372
+ type: DataType.STRING,
373
+ columnName: 'bar',
374
+ },
375
+ baz: {
376
+ type: DataType.STRING,
377
+ columnName: 'qux',
378
+ },
379
+ },
380
+ });
381
+ const A = await schema
382
+ .getService(AdapterRegistry)
383
+ .getAdapter('mongodb');
384
+ const res1 = A._buildSort('model', ['foo', 'baz']);
385
+ const res2 = A._buildSort('model', ['foo DESC', 'baz ASC']);
386
+ const res3 = A._buildSort('model', ['foo ASC', 'baz DESC']);
387
+ expect(res1).to.be.eql({bar: 1, qux: 1});
388
+ expect(res2).to.be.eql({bar: -1, qux: 1});
389
+ expect(res3).to.be.eql({bar: 1, qux: -1});
390
+ });
391
+ });
392
+ });
393
+
44
394
  describe('create', function () {
45
395
  it('generates a new identifier when a value of a primary key is not provided', async function () {
46
396
  const schema = createSchema();
@@ -949,6 +1299,21 @@ describe('MongodbAdapter', function () {
949
1299
  .findOne({_id: oid});
950
1300
  expect(rawData).to.be.eql({_id: oid, fooCol: 15, barCol: 25, bazCol: 35});
951
1301
  });
1302
+
1303
+ it('does not throws an error if nothing changed', async function () {
1304
+ const schema = createSchema();
1305
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
1306
+ const rep = schema.getRepository('model');
1307
+ const created = await rep.create({foo: 10});
1308
+ const id = created[DEF_PK];
1309
+ const replaced = await rep.replaceById(id, {foo: 10});
1310
+ expect(replaced).to.be.eql({[DEF_PK]: id, foo: 10});
1311
+ const oid = new ObjectId(id);
1312
+ const rawData = await MDB_CLIENT.db()
1313
+ .collection('model')
1314
+ .findOne({_id: oid});
1315
+ expect(rawData).to.be.eql({_id: oid, foo: 10});
1316
+ });
952
1317
  });
953
1318
 
954
1319
  describe('patchById', function () {
@@ -1301,6 +1666,21 @@ describe('MongodbAdapter', function () {
1301
1666
  .findOne({_id: oid});
1302
1667
  expect(rawData).to.be.eql({_id: oid, fooCol: 15, barCol: 25, bazCol: 35});
1303
1668
  });
1669
+
1670
+ it('does not throws an error if nothing changed', async function () {
1671
+ const schema = createSchema();
1672
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
1673
+ const rep = schema.getRepository('model');
1674
+ const created = await rep.create({foo: 10});
1675
+ const id = created[DEF_PK];
1676
+ const patched = await rep.patchById(id, {foo: 10});
1677
+ expect(patched).to.be.eql({[DEF_PK]: id, foo: 10});
1678
+ const oid = new ObjectId(id);
1679
+ const rawData = await MDB_CLIENT.db()
1680
+ .collection('model')
1681
+ .findOne({_id: oid});
1682
+ expect(rawData).to.be.eql({_id: oid, foo: 10});
1683
+ });
1304
1684
  });
1305
1685
 
1306
1686
  describe('find', function () {