@e22m4u/js-repository-mongodb-adapter 0.0.19 → 0.0.21

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.19",
3
+ "version": "0.0.21",
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.34"
41
+ "@e22m4u/js-repository": "~0.0.36"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@commitlint/cli": "^17.7.1",
@@ -364,15 +364,24 @@ export class MongodbAdapter extends Adapter {
364
364
  * @private
365
365
  */
366
366
  _buildQuery(modelName, clause) {
367
+ if (clause == null) return;
368
+ if (typeof clause !== 'object' || Array.isArray(clause))
369
+ throw new InvalidArgumentError(
370
+ 'The provided option "where" should be an Object, but %v given.',
371
+ clause,
372
+ );
367
373
  const query = {};
368
- if (!clause || typeof clause !== 'object') return query;
369
374
  const idPropName = this._getIdPropName(modelName);
370
375
  Object.keys(clause).forEach(key => {
371
376
  let cond = clause[key];
372
377
  // and/or/nor clause
373
378
  if (key === 'and' || key === 'or' || key === 'nor') {
374
- if (Array.isArray(cond))
375
- cond = cond.map(c => this._buildQuery(modelName, c));
379
+ if (cond == null) return;
380
+ if (!Array.isArray(cond))
381
+ throw new InvalidOperatorValueError(key, 'an Array', cond);
382
+ if (cond.length === 0) return;
383
+ cond = cond.map(c => this._buildQuery(modelName, c));
384
+ cond = cond.filter(c => c != null);
376
385
  query['$' + key] = cond;
377
386
  return;
378
387
  }
@@ -527,7 +536,7 @@ export class MongodbAdapter extends Adapter {
527
536
  // unknown
528
537
  query[key] = cond;
529
538
  });
530
- return query;
539
+ return Object.keys(query).length ? query : undefined;
531
540
  }
532
541
 
533
542
  /**
@@ -579,8 +588,8 @@ export class MongodbAdapter extends Adapter {
579
588
  modelData[idPropName] = id;
580
589
  const tableData = this._toDatabase(modelName, modelData);
581
590
  const table = this._getCollection(modelName);
582
- const {modifiedCount} = await table.replaceOne({_id: id}, tableData);
583
- if (modifiedCount < 1)
591
+ const {matchedCount} = await table.replaceOne({_id: id}, tableData);
592
+ if (matchedCount < 1)
584
593
  throw new InvalidArgumentError('Identifier %v is not found.', String(id));
585
594
  const projection = this._buildProjection(
586
595
  modelName,
@@ -605,8 +614,8 @@ export class MongodbAdapter extends Adapter {
605
614
  delete modelData[idPropName];
606
615
  const tableData = this._toDatabase(modelName, modelData);
607
616
  const table = this._getCollection(modelName);
608
- const {modifiedCount} = await table.updateOne({_id: id}, {$set: tableData});
609
- if (modifiedCount < 1)
617
+ const {matchedCount} = await table.updateOne({_id: id}, {$set: tableData});
618
+ if (matchedCount < 1)
610
619
  throw new InvalidArgumentError('Identifier %v is not found.', String(id));
611
620
  const projection = this._buildProjection(
612
621
  modelName,
@@ -7,6 +7,7 @@ import {DataType} from '@e22m4u/js-repository';
7
7
  import {createMongodbUrl} from './utils/index.js';
8
8
  import {MongodbAdapter} from './mongodb-adapter.js';
9
9
  import {AdapterRegistry} from '@e22m4u/js-repository';
10
+ import {InvalidOperatorValueError} from '@e22m4u/js-repository';
10
11
  import {DEFAULT_PRIMARY_KEY_PROPERTY_NAME as DEF_PK} from '@e22m4u/js-repository';
11
12
 
12
13
  const CONFIG = {
@@ -274,18 +275,34 @@ describe('MongodbAdapter', function () {
274
275
  expect(throwable(null)()).to.be.undefined;
275
276
  });
276
277
 
277
- it('recognizes direction by the given direction flag', async function () {
278
+ it('uses ascending direction by default', async function () {
278
279
  const schema = createSchema();
279
280
  schema.defineModel({name: 'model', datasource: 'mongodb'});
280
281
  const A = await schema
281
282
  .getService(AdapterRegistry)
282
283
  .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});
284
+ const res = A._buildSort('model', 'foo');
285
+ expect(res).to.be.eql({foo: 1});
286
+ });
287
+
288
+ it('uses descending direction by the "DESC" flag', async function () {
289
+ const schema = createSchema();
290
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
291
+ const A = await schema
292
+ .getService(AdapterRegistry)
293
+ .getAdapter('mongodb');
294
+ const res = A._buildSort('model', 'foo DESC');
295
+ expect(res).to.be.eql({foo: -1});
296
+ });
297
+
298
+ it('uses ascending direction by the "ASC" flag', async function () {
299
+ const schema = createSchema();
300
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
301
+ const A = await schema
302
+ .getService(AdapterRegistry)
303
+ .getAdapter('mongodb');
304
+ const res = A._buildSort('model', 'foo ASC');
305
+ expect(res).to.be.eql({foo: 1});
289
306
  });
290
307
 
291
308
  it('converts the given property name to the column name', async function () {
@@ -348,18 +365,44 @@ describe('MongodbAdapter', function () {
348
365
  expect(throwable(['bar', 'baz'])()).to.be.eql({bar: 1, baz: 1});
349
366
  });
350
367
 
351
- it('recognizes direction by the given direction flag', async function () {
368
+ it('uses ascending direction by default', async function () {
369
+ const schema = createSchema();
370
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
371
+ const A = await schema
372
+ .getService(AdapterRegistry)
373
+ .getAdapter('mongodb');
374
+ const res = A._buildSort('model', ['foo', 'bar']);
375
+ expect(res).to.be.eql({foo: 1, bar: 1});
376
+ });
377
+
378
+ it('uses descending direction by the "DESC" flag', async function () {
379
+ const schema = createSchema();
380
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
381
+ const A = await schema
382
+ .getService(AdapterRegistry)
383
+ .getAdapter('mongodb');
384
+ const res = A._buildSort('model', ['foo DESC', 'bar DESC']);
385
+ expect(res).to.be.eql({foo: -1, bar: -1});
386
+ });
387
+
388
+ it('uses ascending direction by the "ASC" flag', async function () {
352
389
  const schema = createSchema();
353
390
  schema.defineModel({name: 'model', datasource: 'mongodb'});
354
391
  const A = await schema
355
392
  .getService(AdapterRegistry)
356
393
  .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});
394
+ const res = A._buildSort('model', ['foo ASC', 'bar ASC']);
395
+ expect(res).to.be.eql({foo: 1, bar: 1});
396
+ });
397
+
398
+ it('uses multiple directions by the multiple fields', async function () {
399
+ const schema = createSchema();
400
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
401
+ const A = await schema
402
+ .getService(AdapterRegistry)
403
+ .getAdapter('mongodb');
404
+ const res = A._buildSort('model', ['foo', 'bar DESC', 'baz ASC']);
405
+ expect(res).to.be.eql({foo: 1, bar: -1, baz: 1});
363
406
  });
364
407
 
365
408
  it('converts the given property names to column names', async function () {
@@ -391,6 +434,120 @@ describe('MongodbAdapter', function () {
391
434
  });
392
435
  });
393
436
 
437
+ describe('_buildQuery', function () {
438
+ it('requires the second argument to be an object', async function () {
439
+ const schema = createSchema();
440
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
441
+ const A = await schema.getService(AdapterRegistry).getAdapter('mongodb');
442
+ const throwable = v => () => A._buildQuery('model', v);
443
+ const error = v =>
444
+ format(
445
+ 'The provided option "where" should be an Object, but %s given.',
446
+ v,
447
+ );
448
+ expect(throwable('str')).to.throw(error('"str"'));
449
+ expect(throwable('')).to.throw(error('""'));
450
+ expect(throwable(10)).to.throw(error('10'));
451
+ expect(throwable(0)).to.throw(error('0'));
452
+ expect(throwable(true)).to.throw(error('true'));
453
+ expect(throwable(false)).to.throw(error('false'));
454
+ expect(throwable({foo: 'bar'})()).to.be.eql({foo: 'bar'});
455
+ expect(throwable({})()).to.be.undefined;
456
+ expect(throwable(undefined)()).to.be.undefined;
457
+ expect(throwable(null)()).to.be.undefined;
458
+ });
459
+
460
+ it('converts the property names to column names', async function () {
461
+ const schema = createSchema();
462
+ schema.defineModel({
463
+ name: 'model',
464
+ datasource: 'mongodb',
465
+ properties: {
466
+ foo: {
467
+ type: DataType.STRING,
468
+ columnName: 'bar',
469
+ },
470
+ baz: {
471
+ type: DataType.STRING,
472
+ columnName: 'qux',
473
+ },
474
+ },
475
+ });
476
+ const A = await schema.getService(AdapterRegistry).getAdapter('mongodb');
477
+ const res = A._buildQuery('model', {foo: 'a1', baz: null});
478
+ expect(res).to.be.eql({bar: 'a1', qux: null});
479
+ });
480
+
481
+ it('converts strings of the ObjectId to instances', async function () {
482
+ const schema = createSchema();
483
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
484
+ const A = await schema.getService(AdapterRegistry).getAdapter('mongodb');
485
+ const oid1 = new ObjectId();
486
+ const oid2 = new ObjectId();
487
+ const id1 = String(oid1);
488
+ const id2 = String(oid2);
489
+ const res = A._buildQuery('model', {foo: id1, bar: id2});
490
+ expect(res.foo).to.be.instanceof(ObjectId);
491
+ expect(res.bar).to.be.instanceof(ObjectId);
492
+ expect(res.foo).to.be.eql(oid1);
493
+ expect(res.bar).to.be.eql(oid2);
494
+ });
495
+
496
+ it('adds "$" prefix to the "and", "or" and "nor" operator keys', async function () {
497
+ const input = {
498
+ and: [{foo: 'a1'}],
499
+ or: [{foo: 'a2'}],
500
+ nor: [{foo: 'a3'}],
501
+ };
502
+ const schema = createSchema();
503
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
504
+ const A = await schema.getService(AdapterRegistry).getAdapter('mongodb');
505
+ const res = A._buildQuery('model', input);
506
+ expect(res).to.be.eql({
507
+ $and: [{foo: 'a1'}],
508
+ $or: [{foo: 'a2'}],
509
+ $nor: [{foo: 'a3'}],
510
+ });
511
+ });
512
+
513
+ it('does not include an empty array of "and", "or" and "nor" operators', async function () {
514
+ const input1 = {foo: 'a1', and: [], or: [], nor: []};
515
+ const input2 = {foo: 'a2', and: undefined, or: undefined, nor: undefined};
516
+ const input3 = {foo: 'a3', and: null, or: null, nor: null};
517
+ const schema = createSchema();
518
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
519
+ const A = await schema.getService(AdapterRegistry).getAdapter('mongodb');
520
+ const res1 = A._buildQuery('model', input1);
521
+ const res2 = A._buildQuery('model', input2);
522
+ const res3 = A._buildQuery('model', input3);
523
+ expect(res1).to.be.eql({foo: 'a1'});
524
+ expect(res2).to.be.eql({foo: 'a2'});
525
+ expect(res3).to.be.eql({foo: 'a3'});
526
+ });
527
+
528
+ it('operators "and", "or" and "nor" are require an array of objects', async function () {
529
+ const schema = createSchema();
530
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
531
+ const A = await schema.getService(AdapterRegistry).getAdapter('mongodb');
532
+ const throwable = (k, v) => () => A._buildQuery('model', {[k]: v});
533
+ const error = (k, v) => {
534
+ const e = new InvalidOperatorValueError(k, 'an Array', v);
535
+ return e.message;
536
+ };
537
+ const testOf = v => {
538
+ expect(throwable('and', v)).to.throw(error('and', v));
539
+ expect(throwable('or', v)).to.throw(error('or', v));
540
+ expect(throwable('nor', v)).to.throw(error('nor', v));
541
+ };
542
+ testOf('str');
543
+ testOf('');
544
+ testOf(10);
545
+ testOf(0);
546
+ testOf(true);
547
+ testOf(false);
548
+ });
549
+ });
550
+
394
551
  describe('create', function () {
395
552
  it('generates a new identifier when a value of a primary key is not provided', async function () {
396
553
  const schema = createSchema();
@@ -1299,6 +1456,21 @@ describe('MongodbAdapter', function () {
1299
1456
  .findOne({_id: oid});
1300
1457
  expect(rawData).to.be.eql({_id: oid, fooCol: 15, barCol: 25, bazCol: 35});
1301
1458
  });
1459
+
1460
+ it('does not throws an error if nothing changed', async function () {
1461
+ const schema = createSchema();
1462
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
1463
+ const rep = schema.getRepository('model');
1464
+ const created = await rep.create({foo: 10});
1465
+ const id = created[DEF_PK];
1466
+ const replaced = await rep.replaceById(id, {foo: 10});
1467
+ expect(replaced).to.be.eql({[DEF_PK]: id, foo: 10});
1468
+ const oid = new ObjectId(id);
1469
+ const rawData = await MDB_CLIENT.db()
1470
+ .collection('model')
1471
+ .findOne({_id: oid});
1472
+ expect(rawData).to.be.eql({_id: oid, foo: 10});
1473
+ });
1302
1474
  });
1303
1475
 
1304
1476
  describe('patchById', function () {
@@ -1651,6 +1823,21 @@ describe('MongodbAdapter', function () {
1651
1823
  .findOne({_id: oid});
1652
1824
  expect(rawData).to.be.eql({_id: oid, fooCol: 15, barCol: 25, bazCol: 35});
1653
1825
  });
1826
+
1827
+ it('does not throws an error if nothing changed', async function () {
1828
+ const schema = createSchema();
1829
+ schema.defineModel({name: 'model', datasource: 'mongodb'});
1830
+ const rep = schema.getRepository('model');
1831
+ const created = await rep.create({foo: 10});
1832
+ const id = created[DEF_PK];
1833
+ const patched = await rep.patchById(id, {foo: 10});
1834
+ expect(patched).to.be.eql({[DEF_PK]: id, foo: 10});
1835
+ const oid = new ObjectId(id);
1836
+ const rawData = await MDB_CLIENT.db()
1837
+ .collection('model')
1838
+ .findOne({_id: oid});
1839
+ expect(rawData).to.be.eql({_id: oid, foo: 10});
1840
+ });
1654
1841
  });
1655
1842
 
1656
1843
  describe('find', function () {