@onehat/data 1.6.13 → 1.7.3

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.
@@ -63,6 +63,43 @@ describe('Repository Base', function() {
63
63
  })
64
64
  expect(repository.id).to.be.not.eq('foo');
65
65
  });
66
+
67
+ it('_createMethods', async function() {
68
+
69
+ // There is no test method by default
70
+ expect(this.repository.testMethod).to.not.exist;
71
+
72
+ // Set up custom repository with testMethod
73
+ const schema1 = new Schema({
74
+ name: 'baz',
75
+ model: {
76
+ idProperty: 'key',
77
+ displayProperty: 'value',
78
+ properties: [
79
+ { name: 'key', type: 'int' },
80
+ { name: 'value' },
81
+ ],
82
+ },
83
+ repository: {
84
+ type: 'null',
85
+ methods: {
86
+ testMethod: function() {
87
+ this.bar = 'test me';
88
+ },
89
+ },
90
+ },
91
+ }),
92
+ repository = new this.Repository({
93
+ schema: schema1,
94
+ data: [],
95
+ });
96
+ await repository.initialize();
97
+ schema1.setBoundRepository(repository);
98
+
99
+ // Perform testMethod test
100
+ repository.testMethod();
101
+ expect(repository.bar).to.be.eq('test me');
102
+ });
66
103
  });
67
104
 
68
105
  describe('loading', function() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/data",
3
- "version": "1.6.13",
3
+ "version": "1.7.3",
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
@@ -165,12 +165,11 @@ class Entity extends EventEmitter {
165
165
  throw Error('this._createMethods is no longer valid. Entity has been destroyed.');
166
166
  }
167
167
  const methodDefinitions = this.schema.entity.methods;
168
- if (_.isEmpty(methodDefinitions)) {
169
- return;
168
+ if (!_.isEmpty(methodDefinitions)) {
169
+ _.each(methodDefinitions, (method, name) => {
170
+ this[name] = method; // NOTE: Methods must be defined in schema as "function() {}", not as "() => {}" so "this" will be assigned correctly
171
+ });
170
172
  }
171
- _.each(methodDefinitions, (method, name) => {
172
- this[name] = method; // NOTE: Methods must be defined in schema as "function() {}", not as "() => {}" so "this" will be assigned correctly
173
- });
174
173
  }
175
174
 
176
175
  /**
@@ -257,15 +256,17 @@ class Entity extends EventEmitter {
257
256
  * This is mainly for updating Entity with new data
258
257
  * from remote storage medium.
259
258
  * Assumes (and sets) isPersisted === true.
259
+ * Assumes (and sets) isTempId === false.
260
260
  * @param {array} originalData - Raw data to load into entity.
261
261
  */
262
262
  loadOriginalData = (originalData) => {
263
263
  if (this.isDestroyed) {
264
264
  throw Error('this.loadOriginalData is no longer valid. Entity has been destroyed.');
265
265
  }
266
- this._originalData = originalData || {};
267
266
  this.isPersisted = true;
267
+ this._originalData = originalData || {};
268
268
  this.reset();
269
+ this.getIdProperty().isTempId = false;
269
270
  }
270
271
 
271
272
  /**
@@ -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
  },
@@ -235,9 +238,10 @@ class AjaxRepository extends Repository {
235
238
  matches = name.match(re),
236
239
  paramsToChange = isBaseParam ? this._baseParams : this._params;
237
240
 
241
+ let first, second;
238
242
  if (matches) { // name has array notation like 'conditions[username]'
239
- const first = matches[1],
240
- second = matches[2];
243
+ first = matches[1],
244
+ second = matches[2];
241
245
  if (paramsToChange && !paramsToChange.hasOwnProperty(first)) {
242
246
  paramsToChange[first] = [];
243
247
  }
@@ -498,7 +502,7 @@ class AjaxRepository extends Repository {
498
502
  * @returns {promise} - Axios Promise.
499
503
  * @private
500
504
  */
501
- _doAdd(entity) {
505
+ _doAdd(entity) { // standard function notation
502
506
  if (!this.api.add) {
503
507
  throw new Error('No "add" api endpoint defined.');
504
508
  }
@@ -528,13 +532,57 @@ class AjaxRepository extends Repository {
528
532
  entity.loadOriginalData(root[0]);
529
533
  });
530
534
  }
535
+
536
+ /**
537
+ * Helper for save.
538
+ * Add multiple entities to storage medium
539
+ * @param {array} entities - Entities
540
+ * @returns {promise} - Axios Promise.
541
+ * @private
542
+ */
543
+ _doBatchAdd(entities) { // standard function notation
544
+ if (!this.api.batchAdd) {
545
+ throw new Error('No "batchAdd" api endpoint defined.');
546
+ }
547
+
548
+ this._operations.add = true;
549
+
550
+ const method = this.methods.add,
551
+ url = this.api.batchAdd,
552
+ data = {
553
+ entities: _.map(entities, entity => entity.submitValues),
554
+ };
555
+
556
+ return this._send(method, url, data)
557
+ .then(result => {
558
+ if (this.debugMode) {
559
+ console.log(this.api.batchAdd + ' result', result);
560
+ }
561
+ const {
562
+ root,
563
+ success,
564
+ total,
565
+ message
566
+ } = this._processServerResponse(result);
567
+
568
+ if (!success) {
569
+ throw new Error(message);
570
+ }
571
+
572
+ // Reload each entity with new data
573
+ // TODO: Check this
574
+ _.each(entities, (entity, ix) => {
575
+ entity.loadOriginalData(root[ix]);
576
+ });
577
+ });
578
+ }
531
579
 
532
580
  /**
533
581
  * Helper for save.
534
582
  * @returns {promise} - Axios Promise.
535
583
  * @private
536
584
  */
537
- _doEdit(entity) {
585
+ _doEdit(entity) { // standard function notation
538
586
  if (!this.api.edit) {
539
587
  throw new Error('No "edit" api endpoint defined.');
540
588
  }
@@ -565,12 +613,56 @@ class AjaxRepository extends Repository {
565
613
  });
566
614
  }
567
615
 
616
+ /**
617
+ * Helper for save.
618
+ * Edit multiple entities in storage medium
619
+ * @param {array} entities - Entities
620
+ * @returns {promise} - Axios Promise.
621
+ * @private
622
+ */
623
+ _doBatchEdit(entities) { // standard function notation
624
+ if (!this.api.batchEdit) {
625
+ throw new Error('No "batchEdit" api endpoint defined.');
626
+ }
627
+
628
+ this._operations.edit = true;
629
+
630
+ const method = this.methods.edit,
631
+ url = this.api.batchEdit,
632
+ data = {
633
+ entities: _.map(entities, entity => entity.submitValues),
634
+ };
635
+
636
+ return this._send(method, url, data)
637
+ .then(result => {
638
+ if (this.debugMode) {
639
+ console.log(this.api.batchEdit + ' result', result);
640
+ }
641
+ const {
642
+ root,
643
+ success,
644
+ total,
645
+ message
646
+ } = this._processServerResponse(result);
647
+
648
+ if (!success) {
649
+ throw new Error(message);
650
+ }
651
+
652
+ // Reload each entity with new data
653
+ // TODO: Check this
654
+ _.each(entities, (entity, ix) => {
655
+ entity.loadOriginalData(root[ix]);
656
+ });
657
+ });
658
+ }
659
+
568
660
  /**
569
661
  * Helper for save.
570
662
  * @returns {promise} - Axios Promise.
571
663
  * @private
572
664
  */
573
- _doDelete(entity) {
665
+ _doDelete(entity) { // standard function notation
574
666
  if (!this.api.delete) {
575
667
  throw new Error('No "delete" api endpoint defined.');
576
668
  }
@@ -604,9 +696,65 @@ class AjaxRepository extends Repository {
604
696
  });
605
697
  }
606
698
 
699
+ /**
700
+ * Helper for save.
701
+ * Delete multiple entities from storage medium
702
+ * @param {array} entities - Entities
703
+ * @returns {promise} - Axios Promise.
704
+ * @private
705
+ */
706
+ _doBatchDelete(entities) { // standard function notation
707
+ if (!this.api.batchDelete) {
708
+ throw new Error('No "batchDelete" api endpoint defined.');
709
+ }
710
+
711
+ this._operations.delete = true;
712
+
713
+ const method = this.methods.delete,
714
+ url = this.api.batchDelete,
715
+ ids = _.map(entities, entity => entity.id),
716
+ data = { ids, };
717
+
718
+ return this._send(method, url, data)
719
+ .then(result => {
720
+ if (this.debugMode) {
721
+ console.log(this.api.batchDelete + ' result', result);
722
+ }
723
+ const {
724
+ root,
725
+ success,
726
+ total,
727
+ message
728
+ } = this._processServerResponse(result);
729
+
730
+ if (!success) {
731
+ throw new Error(message);
732
+ }
733
+
734
+ // Delete it from this.entities
735
+ this.entities = _.filter(this.entities, (entity) => {
736
+ const deleteIt = ids.includes(entity.id);
737
+ if (deleteIt) {
738
+ entity.destroy();
739
+ }
740
+ return !deleteIt;
741
+ });
742
+ });
743
+ }
744
+
745
+ /**
746
+ * Helper for save.
747
+ * Tells repository to delete entity without ever having saved it
748
+ * to storage medium
749
+ * @private
750
+ */
607
751
  _doDeleteNonPersisted(entity) {
608
752
  this.entities = _.filter(this.entities, (item) => {
609
- return item !== entity;
753
+ const match = item === entity;
754
+ if (match) {
755
+ entity.destroy();
756
+ }
757
+ return !match;
610
758
  });
611
759
 
612
760
  return true;
@@ -687,19 +835,19 @@ class AjaxRepository extends Repository {
687
835
  */
688
836
  _finalizeSave = (promises) => {
689
837
  return this.axios.all(promises)
690
- .then(this.axios.spread((...batchOperationResults) => {
691
- // All requests are now complete
692
-
693
- this.isSaving = false;
694
- this.emit('save', batchOperationResults);
695
-
696
- // Do we need to reload?
697
- if (this._operations.add || this._operations.delete) {
698
- this.reload();
699
- } else {
700
- this.emit('changeData', this.entities);
701
- }
702
- }));
838
+ .then(this.axios.spread((...batchOperationResults) => {
839
+ // All requests are now complete
840
+
841
+ this.isSaving = false;
842
+ this.emit('save', batchOperationResults);
843
+
844
+ // Do we need to reload?
845
+ if (this._operations.add || this._operations.delete) {
846
+ this.reload();
847
+ } else {
848
+ this.emit('changeData', this.entities);
849
+ }
850
+ }));
703
851
  }
704
852
 
705
853
  }
@@ -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',
@@ -265,10 +265,28 @@ export default class Repository extends EventEmitter {
265
265
  await this.sort();
266
266
  }
267
267
 
268
+ this._createMethods();
269
+
268
270
  this.isInitialized = true;
269
271
  this.emit('initialize');
270
272
  }
271
273
 
274
+ /**
275
+ * Creates the methods for this Repository, based on Schema.
276
+ * @private
277
+ */
278
+ _createMethods = () => {
279
+ if (this.isDestroyed) {
280
+ throw Error('this._createMethods is no longer valid. Repository has been destroyed.');
281
+ }
282
+ const methodDefinitions = this.schema.repository.methods;
283
+ if (!_.isEmpty(methodDefinitions)) {
284
+ _.each(methodDefinitions, (method, name) => {
285
+ this[name] = method; // NOTE: Methods must be defined in schema as "function() {}", not as "() => {}" so "this" will be assigned correctly
286
+ });
287
+ }
288
+ }
289
+
272
290
 
273
291
  // __ __
274
292
  // / / ____ ____ _____/ /
@@ -1243,23 +1261,26 @@ export default class Repository extends EventEmitter {
1243
1261
 
1244
1262
  const batchOrder = this.batchOrder.split(',');
1245
1263
 
1246
- let n;
1264
+ let n,
1265
+ i,
1266
+ entity,
1267
+ entities,
1268
+ operation,
1269
+ result;
1247
1270
  for (n = 0; n < batchOrder.length; n++) {
1248
- const operation = batchOrder[n];
1249
- let entities;
1271
+ operation = batchOrder[n];
1250
1272
  switch(operation) {
1251
1273
  case 'add':
1252
1274
  entities = this.getNonPersisted();
1253
- if (this.combineBatch) {
1275
+ if (_.size(entities) > 0) {
1276
+ if (this.combineBatch) {
1254
1277
 
1255
- // TODO: Implement combined batch processing
1256
- throw new Error('Combined batch processing not yet implemented');
1257
-
1258
- } else {
1259
- if (_.size(entities) > 0) {
1260
- let i;
1278
+ result = this.batchAsSynchronous ? await this._doBatchAdd(entities) : this._doBatchAdd(entities);
1279
+ results.push(result);
1280
+
1281
+ } else {
1261
1282
  for (i = 0; i < entities.length; i++) {
1262
- const entity = entities[i];
1283
+ entity = entities[i];
1263
1284
 
1264
1285
  if (entity.isDeleted) {
1265
1286
  // This entity is new, but it's also marked for deletion
@@ -1267,26 +1288,23 @@ export default class Repository extends EventEmitter {
1267
1288
  continue;
1268
1289
  }
1269
1290
 
1270
- const result = this.batchAsSynchronous ? await this._doAdd(entity) : this._doAdd(entity);
1291
+ result = this.batchAsSynchronous ? await this._doAdd(entity) : this._doAdd(entity);
1271
1292
  results.push(result);
1272
1293
  }
1273
1294
  }
1274
1295
  }
1275
-
1276
-
1277
1296
  break;
1278
1297
  case 'edit':
1279
1298
  entities = this.getDirty();
1280
- if (this.combineBatch) {
1299
+ if (_.size(entities) > 0) {
1300
+ if (this.combineBatch) {
1281
1301
 
1282
- // TODO: Implement combined batch processing
1283
- throw new Error('Combined batch processing not yet implemented');
1302
+ result = this.batchAsSynchronous ? await this._doBatchEdit(entities) : this._doBatchEdit(entities);
1303
+ results.push(result);
1284
1304
 
1285
- } else {
1286
- if (_.size(entities) > 0) {
1287
- let i;
1305
+ } else {
1288
1306
  for (i = 0; i < entities.length; i++) {
1289
- const entity = entities[i];
1307
+ entity = entities[i];
1290
1308
 
1291
1309
  if (entity.isDeleted) {
1292
1310
  // This entity is new, but it's also marked for deletion
@@ -1294,7 +1312,7 @@ export default class Repository extends EventEmitter {
1294
1312
  continue;
1295
1313
  }
1296
1314
 
1297
- const result = this.batchAsSynchronous ? await this._doEdit(entity) : this._doEdit(entity);
1315
+ result = this.batchAsSynchronous ? await this._doEdit(entity) : this._doEdit(entity);
1298
1316
  results.push(result);
1299
1317
  }
1300
1318
  }
@@ -1302,18 +1320,16 @@ export default class Repository extends EventEmitter {
1302
1320
  break;
1303
1321
  case 'delete':
1304
1322
  entities = this.getDeleted();
1305
- if (this.combineBatch) {
1323
+ if (_.size(entities) > 0) {
1324
+ if (this.combineBatch) {
1306
1325
 
1307
- // TODO: Implement combined batch processing
1308
- throw new Error('Combined batch processing not yet implemented');
1326
+ result = this.batchAsSynchronous ? await this._doBatchDelete(entities) : this._doBatchDelete(entities);
1327
+ results.push(result);
1309
1328
 
1310
- } else {
1311
- if (_.size(entities) > 0) {
1312
- let i;
1329
+ } else {
1313
1330
  for (i = 0; i < entities.length; i++) {
1314
- const entity = entities[i];
1331
+ entity = entities[i];
1315
1332
 
1316
- let result;
1317
1333
  if (!entity.isPersisted) {
1318
1334
  result = this.batchAsSynchronous ? await this._doDeleteNonPersisted(entity) : this._doDeleteNonPersisted(entity);
1319
1335
  } else {
@@ -1331,8 +1347,20 @@ export default class Repository extends EventEmitter {
1331
1347
  return await this._finalizeSave(results);
1332
1348
  }
1333
1349
 
1350
+
1351
+ /**
1352
+ * Helper for save.
1353
+ * Add multiple entities to storage medium
1354
+ * @param {array} entities - Entities
1355
+ * @private
1356
+ * @abstract
1357
+ */
1358
+ _doBatchAdd(entities) { // standard function notation
1359
+ throw new Error('_doBatchAdd must be implemented by Repository subclass');
1360
+ }
1361
+
1334
1362
  /**
1335
- * Helper for save().
1363
+ * Helper for save.
1336
1364
  * Add entity to storage medium
1337
1365
  * @param {object} entity - Entity
1338
1366
  * @private
@@ -1343,7 +1371,18 @@ export default class Repository extends EventEmitter {
1343
1371
  }
1344
1372
 
1345
1373
  /**
1346
- * Helper for save().
1374
+ * Helper for save.
1375
+ * Edit multiple entities in storage medium
1376
+ * @param {array} entities - Entities
1377
+ * @private
1378
+ * @abstract
1379
+ */
1380
+ _doBatchEdit(entities) { // standard function notation
1381
+ throw new Error('_doBatchEdit must be implemented by Repository subclass');
1382
+ }
1383
+
1384
+ /**
1385
+ * Helper for save.
1347
1386
  * Mark entity as saved
1348
1387
  * @param {object} entity - Entity
1349
1388
  * @private
@@ -1354,7 +1393,18 @@ export default class Repository extends EventEmitter {
1354
1393
  }
1355
1394
 
1356
1395
  /**
1357
- * Helper for save().
1396
+ * Helper for save.
1397
+ * Delete multiple entities from storage medium
1398
+ * @param {array} entities - Entities
1399
+ * @private
1400
+ * @abstract
1401
+ */
1402
+ _doBatchDelete(entities) { // standard function notation
1403
+ throw new Error('_doBatchDelete must be implemented by Repository subclass');
1404
+ }
1405
+
1406
+ /**
1407
+ * Helper for save.
1358
1408
  * Delete entity from storage medium
1359
1409
  * @param {object} entity - Entity
1360
1410
  * @private
@@ -1365,8 +1415,8 @@ export default class Repository extends EventEmitter {
1365
1415
  }
1366
1416
 
1367
1417
  /**
1368
- * Helper for save().
1369
- * Tells storage medium to delete entity without ever having saved it
1418
+ * Helper for save.
1419
+ * Tells repository to delete entity without ever having saved it
1370
1420
  * to storage medium
1371
1421
  * @private
1372
1422
  */