@onehat/data 1.6.10 → 1.7.0

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.
@@ -630,6 +630,15 @@ describe('Repository Base', function() {
630
630
  expect(_.isEqual(entity, dirty[0])).to.be.true;
631
631
  });
632
632
 
633
+ it('isDirty', function() {
634
+ expect(this.repository.isDirty).to.be.false;
635
+
636
+ this.repository.setAutoSave(false);
637
+ const entity = this.repository.getByIx(0);
638
+ entity.value = 'test';
639
+ expect(this.repository.isDirty).to.be.true;
640
+ });
641
+
633
642
  it('getDeleted', function() {
634
643
  this.repository.setAutoSave(false);
635
644
  const entity = this.repository.getByIx(0);
@@ -722,6 +731,7 @@ describe('Repository Base', function() {
722
731
  // undeleteByRange
723
732
  // undeleteBy
724
733
  // undeleteById
734
+ // undeleteDeleted
725
735
 
726
736
  });
727
737
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/data",
3
- "version": "1.6.10",
3
+ "version": "1.7.0",
4
4
  "description": "JS data modeling package with adapters for many storage mediums.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/Entity.js CHANGED
@@ -257,15 +257,17 @@ class Entity extends EventEmitter {
257
257
  * This is mainly for updating Entity with new data
258
258
  * from remote storage medium.
259
259
  * Assumes (and sets) isPersisted === true.
260
+ * Assumes (and sets) isTempId === false.
260
261
  * @param {array} originalData - Raw data to load into entity.
261
262
  */
262
263
  loadOriginalData = (originalData) => {
263
264
  if (this.isDestroyed) {
264
265
  throw Error('this.loadOriginalData is no longer valid. Entity has been destroyed.');
265
266
  }
266
- this._originalData = originalData || {};
267
267
  this.isPersisted = true;
268
+ this._originalData = originalData || {};
268
269
  this.reset();
270
+ this.getIdProperty().isTempId = false;
269
271
  }
270
272
 
271
273
  /**
@@ -57,7 +57,6 @@ export default class IntegerProperty extends Property {
57
57
  }
58
58
  return id;
59
59
  }
60
-
61
60
 
62
61
  };
63
62
 
@@ -28,10 +28,13 @@ class AjaxRepository extends Repository {
28
28
  * @member {object} api - List of relative URIs to API endpoints.
29
29
  */
30
30
  api: {
31
- add: null,
32
31
  get: null,
32
+ add: null,
33
33
  edit: null,
34
34
  delete: null,
35
+ batchAdd: null,
36
+ batchEdit: null,
37
+ batchDelete: null,
35
38
  baseURL: '', // e.g. 'https://example.com/myapp/'
36
39
  },
37
40
 
@@ -39,8 +42,8 @@ class AjaxRepository extends Repository {
39
42
  * @member {object} methods - List of methods for all four CRUD operations
40
43
  */
41
44
  methods: {
42
- add: 'POST',
43
45
  get: 'GET',
46
+ add: 'POST',
44
47
  edit: 'POST',
45
48
  delete: 'POST',
46
49
  },
@@ -377,6 +380,12 @@ class AjaxRepository extends Repository {
377
380
  if (this.debugMode) {
378
381
  console.log('load result ' + this.name, result);
379
382
  }
383
+
384
+ if (this.isDestroyed) {
385
+ // If this repository gets destroyed before it has a chance
386
+ // to process the Ajax request, just ignore the response.
387
+ return;
388
+ }
380
389
 
381
390
  const {
382
391
  root,
@@ -492,7 +501,7 @@ class AjaxRepository extends Repository {
492
501
  * @returns {promise} - Axios Promise.
493
502
  * @private
494
503
  */
495
- _doAdd(entity) {
504
+ _doAdd(entity) { // standard function notation
496
505
  if (!this.api.add) {
497
506
  throw new Error('No "add" api endpoint defined.');
498
507
  }
@@ -522,13 +531,55 @@ class AjaxRepository extends Repository {
522
531
  entity.loadOriginalData(root[0]);
523
532
  });
524
533
  }
534
+
535
+ /**
536
+ * Helper for save.
537
+ * Add multiple entities to storage medium
538
+ * @param {array} entities - Entities
539
+ * @returns {promise} - Axios Promise.
540
+ * @private
541
+ */
542
+ _doBatchAdd(entities) { // standard function notation
543
+ if (!this.api.batchAdd) {
544
+ throw new Error('No "batchAdd" api endpoint defined.');
545
+ }
546
+
547
+ this._operations.add = true;
548
+
549
+ const method = this.methods.add,
550
+ url = this.api.batchAdd,
551
+ data = _.map(entities, entity => entity.submitValues);
552
+
553
+ return this._send(method, url, data)
554
+ .then(result => {
555
+ if (this.debugMode) {
556
+ console.log(this.api.batchAdd + ' result', result);
557
+ }
558
+ const {
559
+ root,
560
+ success,
561
+ total,
562
+ message
563
+ } = this._processServerResponse(result);
564
+
565
+ if (!success) {
566
+ throw new Error(message);
567
+ }
568
+
569
+ // Reload each entity with new data
570
+ // TODO: Check this
571
+ _.each(entities, (entity, ix) => {
572
+ entity.loadOriginalData(root[ix]);
573
+ });
574
+ });
575
+ }
525
576
 
526
577
  /**
527
578
  * Helper for save.
528
579
  * @returns {promise} - Axios Promise.
529
580
  * @private
530
581
  */
531
- _doEdit(entity) {
582
+ _doEdit(entity) { // standard function notation
532
583
  if (!this.api.edit) {
533
584
  throw new Error('No "edit" api endpoint defined.');
534
585
  }
@@ -559,12 +610,54 @@ class AjaxRepository extends Repository {
559
610
  });
560
611
  }
561
612
 
613
+ /**
614
+ * Helper for save.
615
+ * Edit multiple entities in storage medium
616
+ * @param {array} entities - Entities
617
+ * @returns {promise} - Axios Promise.
618
+ * @private
619
+ */
620
+ _doBatchEdit(entities) { // standard function notation
621
+ if (!this.api.batchEdit) {
622
+ throw new Error('No "batchEdit" api endpoint defined.');
623
+ }
624
+
625
+ this._operations.edit = true;
626
+
627
+ const method = this.methods.edit,
628
+ url = this.api.batchEdit,
629
+ data = _.map(entities, entity => entity.submitValues);
630
+
631
+ return this._send(method, url, data)
632
+ .then(result => {
633
+ if (this.debugMode) {
634
+ console.log(this.api.batchEdit + ' result', result);
635
+ }
636
+ const {
637
+ root,
638
+ success,
639
+ total,
640
+ message
641
+ } = this._processServerResponse(result);
642
+
643
+ if (!success) {
644
+ throw new Error(message);
645
+ }
646
+
647
+ // Reload each entity with new data
648
+ // TODO: Check this
649
+ _.each(entities, (entity, ix) => {
650
+ entity.loadOriginalData(root[ix]);
651
+ });
652
+ });
653
+ }
654
+
562
655
  /**
563
656
  * Helper for save.
564
657
  * @returns {promise} - Axios Promise.
565
658
  * @private
566
659
  */
567
- _doDelete(entity) {
660
+ _doDelete(entity) { // standard function notation
568
661
  if (!this.api.delete) {
569
662
  throw new Error('No "delete" api endpoint defined.');
570
663
  }
@@ -598,9 +691,65 @@ class AjaxRepository extends Repository {
598
691
  });
599
692
  }
600
693
 
694
+ /**
695
+ * Helper for save.
696
+ * Delete multiple entities from storage medium
697
+ * @param {array} entities - Entities
698
+ * @returns {promise} - Axios Promise.
699
+ * @private
700
+ */
701
+ _doBatchDelete(entities) { // standard function notation
702
+ if (!this.api.batchDelete) {
703
+ throw new Error('No "batchDelete" api endpoint defined.');
704
+ }
705
+
706
+ this._operations.delete = true;
707
+
708
+ const method = this.methods.delete,
709
+ url = this.api.batchDelete,
710
+ ids = _.map(entities, entity => entity.id),
711
+ data = { ids, };
712
+
713
+ return this._send(method, url, data)
714
+ .then(result => {
715
+ if (this.debugMode) {
716
+ console.log(this.api.batchDelete + ' result', result);
717
+ }
718
+ const {
719
+ root,
720
+ success,
721
+ total,
722
+ message
723
+ } = this._processServerResponse(result);
724
+
725
+ if (!success) {
726
+ throw new Error(message);
727
+ }
728
+
729
+ // Delete it from this.entities
730
+ this.entities = _.filter(this.entities, (entity) => {
731
+ const deleteIt = ids.includes(entity.id);
732
+ if (deleteIt) {
733
+ entity.destroy();
734
+ }
735
+ return !deleteIt;
736
+ });
737
+ });
738
+ }
739
+
740
+ /**
741
+ * Helper for save.
742
+ * Tells repository to delete entity without ever having saved it
743
+ * to storage medium
744
+ * @private
745
+ */
601
746
  _doDeleteNonPersisted(entity) {
602
747
  this.entities = _.filter(this.entities, (item) => {
603
- return item !== entity;
748
+ const match = item === entity;
749
+ if (match) {
750
+ entity.destroy();
751
+ }
752
+ return !match;
604
753
  });
605
754
 
606
755
  return true;
@@ -681,19 +830,19 @@ class AjaxRepository extends Repository {
681
830
  */
682
831
  _finalizeSave = (promises) => {
683
832
  return this.axios.all(promises)
684
- .then(this.axios.spread((...batchOperationResults) => {
685
- // All requests are now complete
686
-
687
- this.isSaving = false;
688
- this.emit('save', batchOperationResults);
689
-
690
- // Do we need to reload?
691
- if (this._operations.add || this._operations.delete) {
692
- this.reload();
693
- } else {
694
- this.emit('changeData', this.entities);
695
- }
696
- }));
833
+ .then(this.axios.spread((...batchOperationResults) => {
834
+ // All requests are now complete
835
+
836
+ this.isSaving = false;
837
+ this.emit('save', batchOperationResults);
838
+
839
+ // Do we need to reload?
840
+ if (this._operations.add || this._operations.delete) {
841
+ this.reload();
842
+ } else {
843
+ this.emit('changeData', this.entities);
844
+ }
845
+ }));
697
846
  }
698
847
 
699
848
  }
@@ -35,10 +35,13 @@ class OneBuildRepository extends AjaxRepository {
35
35
  autoSave: true,
36
36
 
37
37
  api: {
38
- add: this.name + '/extAdd',
39
38
  get: this.name + '/get',
40
- edit: this.name + '/extEdit',
41
- delete: this.name + '/extDelete',
39
+ add: this.name + '/add',
40
+ edit: this.name + '/edit',
41
+ delete: this.name + '/delete',
42
+ batchAdd: this.name + '/batchAdd',
43
+ batchEdit: this.name + '/batchEdit',
44
+ batchDelete: this.name + '/batchDelete',
42
45
  },
43
46
 
44
47
  methods: {
@@ -51,6 +54,8 @@ class OneBuildRepository extends AjaxRepository {
51
54
  messageProperty: 'message',
52
55
 
53
56
  allowsMultiSort: true,
57
+ // batchAsSynchronous: true, // Add directly to schema for now
58
+ // combineBatch: true,
54
59
 
55
60
  // writer: {
56
61
  // type: 'json',
@@ -1181,6 +1181,18 @@ export default class Repository extends EventEmitter {
1181
1181
  return !_.isNil(this.getById(idOrEntity));
1182
1182
  }
1183
1183
 
1184
+ /**
1185
+ * Getter of isDirty for this Repository.
1186
+ * Returns true if any Entities within it are dirty
1187
+ * @return {boolean} isDirty
1188
+ */
1189
+ get isDirty() {
1190
+ if (this.isDestroyed) {
1191
+ throw Error('this.isDirty is no longer valid. Repository has been destroyed.');
1192
+ }
1193
+ return !!this.getDirty().length;
1194
+ }
1195
+
1184
1196
  /**
1185
1197
  * Convenience function
1186
1198
  * Alias for isInRepository
@@ -1231,23 +1243,26 @@ export default class Repository extends EventEmitter {
1231
1243
 
1232
1244
  const batchOrder = this.batchOrder.split(',');
1233
1245
 
1234
- let n;
1246
+ let n,
1247
+ i,
1248
+ entity,
1249
+ entities,
1250
+ operation,
1251
+ result;
1235
1252
  for (n = 0; n < batchOrder.length; n++) {
1236
- const operation = batchOrder[n];
1237
- let entities;
1253
+ operation = batchOrder[n];
1238
1254
  switch(operation) {
1239
1255
  case 'add':
1240
1256
  entities = this.getNonPersisted();
1241
1257
  if (this.combineBatch) {
1242
1258
 
1243
- // TODO: Implement combined batch processing
1244
- throw new Error('Combined batch processing not yet implemented');
1245
-
1259
+ result = this.batchAsSynchronous ? await this._doBatchAdd(entities) : this._doBatchAdd(entities);
1260
+ results.push(result);
1261
+
1246
1262
  } else {
1247
1263
  if (_.size(entities) > 0) {
1248
- let i;
1249
1264
  for (i = 0; i < entities.length; i++) {
1250
- const entity = entities[i];
1265
+ entity = entities[i];
1251
1266
 
1252
1267
  if (entity.isDeleted) {
1253
1268
  // This entity is new, but it's also marked for deletion
@@ -1255,7 +1270,7 @@ export default class Repository extends EventEmitter {
1255
1270
  continue;
1256
1271
  }
1257
1272
 
1258
- const result = this.batchAsSynchronous ? await this._doAdd(entity) : this._doAdd(entity);
1273
+ result = this.batchAsSynchronous ? await this._doAdd(entity) : this._doAdd(entity);
1259
1274
  results.push(result);
1260
1275
  }
1261
1276
  }
@@ -1267,14 +1282,13 @@ export default class Repository extends EventEmitter {
1267
1282
  entities = this.getDirty();
1268
1283
  if (this.combineBatch) {
1269
1284
 
1270
- // TODO: Implement combined batch processing
1271
- throw new Error('Combined batch processing not yet implemented');
1285
+ result = this.batchAsSynchronous ? await this._doBatchEdit(entities) : this._doBatchEdit(entities);
1286
+ results.push(result);
1272
1287
 
1273
1288
  } else {
1274
1289
  if (_.size(entities) > 0) {
1275
- let i;
1276
1290
  for (i = 0; i < entities.length; i++) {
1277
- const entity = entities[i];
1291
+ entity = entities[i];
1278
1292
 
1279
1293
  if (entity.isDeleted) {
1280
1294
  // This entity is new, but it's also marked for deletion
@@ -1282,7 +1296,7 @@ export default class Repository extends EventEmitter {
1282
1296
  continue;
1283
1297
  }
1284
1298
 
1285
- const result = this.batchAsSynchronous ? await this._doEdit(entity) : this._doEdit(entity);
1299
+ result = this.batchAsSynchronous ? await this._doEdit(entity) : this._doEdit(entity);
1286
1300
  results.push(result);
1287
1301
  }
1288
1302
  }
@@ -1292,16 +1306,14 @@ export default class Repository extends EventEmitter {
1292
1306
  entities = this.getDeleted();
1293
1307
  if (this.combineBatch) {
1294
1308
 
1295
- // TODO: Implement combined batch processing
1296
- throw new Error('Combined batch processing not yet implemented');
1309
+ result = this.batchAsSynchronous ? await this._doBatchDelete(entities) : this._doBatchDelete(entities);
1310
+ results.push(result);
1297
1311
 
1298
1312
  } else {
1299
1313
  if (_.size(entities) > 0) {
1300
- let i;
1301
1314
  for (i = 0; i < entities.length; i++) {
1302
- const entity = entities[i];
1315
+ entity = entities[i];
1303
1316
 
1304
- let result;
1305
1317
  if (!entity.isPersisted) {
1306
1318
  result = this.batchAsSynchronous ? await this._doDeleteNonPersisted(entity) : this._doDeleteNonPersisted(entity);
1307
1319
  } else {
@@ -1319,8 +1331,20 @@ export default class Repository extends EventEmitter {
1319
1331
  return await this._finalizeSave(results);
1320
1332
  }
1321
1333
 
1334
+
1335
+ /**
1336
+ * Helper for save.
1337
+ * Add multiple entities to storage medium
1338
+ * @param {array} entities - Entities
1339
+ * @private
1340
+ * @abstract
1341
+ */
1342
+ _doBatchAdd(entities) { // standard function notation
1343
+ throw new Error('_doBatchAdd must be implemented by Repository subclass');
1344
+ }
1345
+
1322
1346
  /**
1323
- * Helper for save().
1347
+ * Helper for save.
1324
1348
  * Add entity to storage medium
1325
1349
  * @param {object} entity - Entity
1326
1350
  * @private
@@ -1331,7 +1355,18 @@ export default class Repository extends EventEmitter {
1331
1355
  }
1332
1356
 
1333
1357
  /**
1334
- * Helper for save().
1358
+ * Helper for save.
1359
+ * Edit multiple entities in storage medium
1360
+ * @param {array} entities - Entities
1361
+ * @private
1362
+ * @abstract
1363
+ */
1364
+ _doBatchEdit(entities) { // standard function notation
1365
+ throw new Error('_doBatchEdit must be implemented by Repository subclass');
1366
+ }
1367
+
1368
+ /**
1369
+ * Helper for save.
1335
1370
  * Mark entity as saved
1336
1371
  * @param {object} entity - Entity
1337
1372
  * @private
@@ -1342,7 +1377,18 @@ export default class Repository extends EventEmitter {
1342
1377
  }
1343
1378
 
1344
1379
  /**
1345
- * Helper for save().
1380
+ * Helper for save.
1381
+ * Delete multiple entities from storage medium
1382
+ * @param {array} entities - Entities
1383
+ * @private
1384
+ * @abstract
1385
+ */
1386
+ _doBatchDelete(entities) { // standard function notation
1387
+ throw new Error('_doBatchDelete must be implemented by Repository subclass');
1388
+ }
1389
+
1390
+ /**
1391
+ * Helper for save.
1346
1392
  * Delete entity from storage medium
1347
1393
  * @param {object} entity - Entity
1348
1394
  * @private
@@ -1353,8 +1399,8 @@ export default class Repository extends EventEmitter {
1353
1399
  }
1354
1400
 
1355
1401
  /**
1356
- * Helper for save().
1357
- * Tells storage medium to delete entity without ever having saved it
1402
+ * Helper for save.
1403
+ * Tells repository to delete entity without ever having saved it
1358
1404
  * to storage medium
1359
1405
  * @private
1360
1406
  */
@@ -1479,7 +1525,7 @@ export default class Repository extends EventEmitter {
1479
1525
  }
1480
1526
 
1481
1527
  /**
1482
- * Deletes a single Entity by its index (zero-indexed) on the current page
1528
+ * Undelete a single Entity by its index (zero-indexed) on the current page
1483
1529
  * @param {integer} ix - Index
1484
1530
  * @return {object} entity - Entity
1485
1531
  */
@@ -1488,7 +1534,7 @@ export default class Repository extends EventEmitter {
1488
1534
  }
1489
1535
 
1490
1536
  /**
1491
- * Deletes multiple Entities by their range of indices
1537
+ * Undelete multiple Entities by their range of indices
1492
1538
  * (zero-indexed) on the current page
1493
1539
  * @param {integer} startIx - Index
1494
1540
  * @param {integer} endIx - Index (inclusive)
@@ -1499,7 +1545,7 @@ export default class Repository extends EventEmitter {
1499
1545
  }
1500
1546
 
1501
1547
  /**
1502
- * Remove multiple Entities by supplied filter function
1548
+ * Undelete multiple Entities by supplied filter function
1503
1549
  * @param {function} fn - Filter function to apply to all entities
1504
1550
  * @return {Entity[]} Entities that passed through filter
1505
1551
  */
@@ -1508,7 +1554,7 @@ export default class Repository extends EventEmitter {
1508
1554
  }
1509
1555
 
1510
1556
  /**
1511
- * Remove a single Entity by its id
1557
+ * Undelete a single Entity by its id
1512
1558
  * @param {integer} id - id of record to retrieve
1513
1559
  * @return {Entity} The Entity with matching id
1514
1560
  */
@@ -1516,6 +1562,14 @@ export default class Repository extends EventEmitter {
1516
1562
  await this.undelete(this.getById(id));
1517
1563
  }
1518
1564
 
1565
+ /**
1566
+ * Undelete all deleted Entities
1567
+ * @return {Entity[]} Entities that passed through filter
1568
+ */
1569
+ undeleteDeleted = async () => {
1570
+ await this.undelete(this.getDeleted());
1571
+ }
1572
+
1519
1573
 
1520
1574
 
1521
1575