@onehat/data 1.7.14 → 1.8.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.
- package/cypress/integration/Entity.spec.js +40 -0
- package/cypress/integration/Repository/Repository.spec.js +24 -0
- package/package.json +1 -1
- package/src/Entity.js +52 -4
- package/src/Repository/Ajax.js +7 -7
- package/src/Repository/Memory.js +17 -3
- package/src/Repository/Null.js +2 -3
- package/src/Repository/Repository.js +41 -5
|
@@ -476,15 +476,40 @@ describe('Entity', function() {
|
|
|
476
476
|
it('setValues', function() {
|
|
477
477
|
expect(this.entity.foo).to.be.eq(1);
|
|
478
478
|
expect(this.entity.bar).to.be.eq('one');
|
|
479
|
+
expect(this.entity.baz).to.be.eq(true);
|
|
479
480
|
expect(this.entity.isDirty).to.be.false;
|
|
480
481
|
|
|
481
482
|
this.entity.setValues({
|
|
482
483
|
foo: 2,
|
|
483
484
|
bar: 'two',
|
|
485
|
+
baz: false,
|
|
484
486
|
});
|
|
485
487
|
|
|
486
488
|
expect(this.entity.foo).to.be.eq(2);
|
|
487
489
|
expect(this.entity.bar).to.be.eq('two');
|
|
490
|
+
expect(this.entity.baz).to.be.eq(false);
|
|
491
|
+
expect(this.entity.isDirty).to.be.true;
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('setRawValues', function() {
|
|
495
|
+
expect(this.entity.foo).to.be.eq(1);
|
|
496
|
+
expect(this.entity.bar).to.be.eq('one');
|
|
497
|
+
expect(this.entity.baz).to.be.eq(true);
|
|
498
|
+
expect(this.entity.isDirty).to.be.false;
|
|
499
|
+
|
|
500
|
+
this.entity.setRawValues({
|
|
501
|
+
foo: 2,
|
|
502
|
+
bar: 'two',
|
|
503
|
+
baz: {
|
|
504
|
+
test: {
|
|
505
|
+
val: false,
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
expect(this.entity.foo).to.be.eq(2);
|
|
511
|
+
expect(this.entity.bar).to.be.eq('two');
|
|
512
|
+
expect(this.entity.baz).to.be.eq(false);
|
|
488
513
|
expect(this.entity.isDirty).to.be.true;
|
|
489
514
|
});
|
|
490
515
|
|
|
@@ -580,6 +605,21 @@ describe('Entity', function() {
|
|
|
580
605
|
this.entity.markStaged(false);
|
|
581
606
|
expect(this.entity.isStaged).to.be.false;
|
|
582
607
|
});
|
|
608
|
+
|
|
609
|
+
it('setValue changed lastModified', function() {
|
|
610
|
+
let earlyLastModified,
|
|
611
|
+
lateLastModified;
|
|
612
|
+
|
|
613
|
+
earlyLastModified = this.entity.lastModified;
|
|
614
|
+
this.entity.setValue('foo', '125');
|
|
615
|
+
lateLastModified = this.entity.lastModified;
|
|
616
|
+
expect(earlyLastModified < lateLastModified).to.be.true;
|
|
617
|
+
|
|
618
|
+
earlyLastModified = this.entity.lastModified;
|
|
619
|
+
this.entity.foo = '126';
|
|
620
|
+
lateLastModified = this.entity.lastModified;
|
|
621
|
+
expect(earlyLastModified < lateLastModified).to.be.true;
|
|
622
|
+
});
|
|
583
623
|
});
|
|
584
624
|
|
|
585
625
|
describe('events', function() {
|
|
@@ -553,6 +553,30 @@ describe('Repository Base', function() {
|
|
|
553
553
|
expect(didFireAdd).to.be.true;
|
|
554
554
|
});
|
|
555
555
|
|
|
556
|
+
it('add already existing', async function() {
|
|
557
|
+
const value = 'another one';
|
|
558
|
+
await this.repository.add({ key: 6, value: 'six' });
|
|
559
|
+
await this.repository.add({ key: 6, value });
|
|
560
|
+
expect(_.size(this.repository.entities)).to.be.eq(6);
|
|
561
|
+
|
|
562
|
+
const entity = this.repository.getById(6);
|
|
563
|
+
expect(entity.value).to.be.eq(value);
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
it('add with an existing id', async function() {
|
|
567
|
+
this.repository.autoSave = false;
|
|
568
|
+
|
|
569
|
+
// ID suppied; should not be temp ID or phantom
|
|
570
|
+
const entity = await this.repository.add({ key: 6, value: 'six' });
|
|
571
|
+
expect(entity.isTempId).to.be.false;
|
|
572
|
+
expect(entity.isPhantom).to.be.false;
|
|
573
|
+
|
|
574
|
+
// No ID suppled. Make it phantom and temp ID
|
|
575
|
+
const entity2 = await this.repository.add({ value: 'seven' });
|
|
576
|
+
expect(entity2.isTempId).to.be.true;
|
|
577
|
+
expect(entity2.isPhantom).to.be.true;
|
|
578
|
+
});
|
|
579
|
+
|
|
556
580
|
it('createStandaloneEntity', async function() {
|
|
557
581
|
const entity = await this.repository.createStandaloneEntity({ key: 6, value: 'six' });
|
|
558
582
|
expect(entity.id).to.be.eq(6);
|
package/package.json
CHANGED
package/src/Entity.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import EventEmitter from '@onehat/events';
|
|
4
4
|
import PropertyTypes from './Property';
|
|
5
|
+
import moment from 'moment';
|
|
5
6
|
import _ from 'lodash';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -129,6 +130,11 @@ class Entity extends EventEmitter {
|
|
|
129
130
|
*/
|
|
130
131
|
this.isDestroyed = false;
|
|
131
132
|
|
|
133
|
+
/**
|
|
134
|
+
* @member {string} lastModified - Last time this entity was modified
|
|
135
|
+
*/
|
|
136
|
+
this.lastModified = null;
|
|
137
|
+
|
|
132
138
|
// This ES6 Proxy allows us to create magic getters and setters for all property values.
|
|
133
139
|
// However, these getters and setters are *not* available within the Entity itself.
|
|
134
140
|
this._proxy = new Proxy(this, {
|
|
@@ -325,6 +331,7 @@ class Entity extends EventEmitter {
|
|
|
325
331
|
if (this.isDeleted) {
|
|
326
332
|
this.undelete();
|
|
327
333
|
}
|
|
334
|
+
this.setLastModified();
|
|
328
335
|
|
|
329
336
|
this.emit('reset', this._proxy);
|
|
330
337
|
}
|
|
@@ -399,6 +406,10 @@ class Entity extends EventEmitter {
|
|
|
399
406
|
}
|
|
400
407
|
return value;
|
|
401
408
|
}
|
|
409
|
+
|
|
410
|
+
setLastModified = () => {
|
|
411
|
+
this.lastModified = moment(new Date()).format('YYYY-MM-DD HH:mm:ss.SSSS');
|
|
412
|
+
}
|
|
402
413
|
|
|
403
414
|
|
|
404
415
|
// ______ __ __
|
|
@@ -990,6 +1001,7 @@ class Entity extends EventEmitter {
|
|
|
990
1001
|
}
|
|
991
1002
|
|
|
992
1003
|
idProperty.isTempId = false;
|
|
1004
|
+
this.setLastModified();
|
|
993
1005
|
|
|
994
1006
|
return isChanged;
|
|
995
1007
|
}
|
|
@@ -1010,23 +1022,58 @@ class Entity extends EventEmitter {
|
|
|
1010
1022
|
propertyValues[propertyName] = rawValue;
|
|
1011
1023
|
return this.setValues(propertyValues);
|
|
1012
1024
|
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Sets Property values
|
|
1028
|
+
* @param {object} rawData - Raw data object. These are prior to mapping,
|
|
1029
|
+
* similar to what you'd use to create a brand new Entity. Make sure *all*
|
|
1030
|
+
* values are here, not just a few.
|
|
1031
|
+
* @return {boolean} isChanged - Whether any values were actually changed
|
|
1032
|
+
*/
|
|
1033
|
+
setRawValues = (rawData) => {
|
|
1034
|
+
if (this.isDestroyed) {
|
|
1035
|
+
throw Error('this.setRawValues is no longer valid. Entity has been destroyed.');
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const [dependentProperties, nonDependentProperties] = _.partition(this.properties, (property) => {
|
|
1039
|
+
return property.hasDepends;
|
|
1040
|
+
});
|
|
1041
|
+
const mappedData = {};
|
|
1042
|
+
function setMappedValue(property) {
|
|
1043
|
+
let rawValue;
|
|
1044
|
+
if (property.hasMapping) {
|
|
1045
|
+
rawValue = Entity.getMappedValue(property.mapping, rawData);
|
|
1046
|
+
} else {
|
|
1047
|
+
rawValue = rawData[property.name];
|
|
1048
|
+
}
|
|
1049
|
+
if (_.isNil(rawValue)) {
|
|
1050
|
+
rawValue = property.getDefaultValue();
|
|
1051
|
+
}
|
|
1052
|
+
mappedData[property.name] = rawValue;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
_.each(nonDependentProperties, setMappedValue);
|
|
1056
|
+
_.each(dependentProperties, setMappedValue);
|
|
1057
|
+
|
|
1058
|
+
return this.setValues(mappedData);
|
|
1059
|
+
}
|
|
1013
1060
|
|
|
1014
1061
|
/**
|
|
1015
1062
|
* Sets Property values
|
|
1016
|
-
* @param {object}
|
|
1063
|
+
* @param {object} data - Raw data object. Keys are Property names, Values are Property values.
|
|
1017
1064
|
* @return {boolean} isChanged - Whether any values were actually changed
|
|
1018
1065
|
* @fires change
|
|
1019
1066
|
*/
|
|
1020
|
-
setValues = (
|
|
1067
|
+
setValues = (data) => {
|
|
1021
1068
|
if (this.isDestroyed) {
|
|
1022
1069
|
throw Error('this.setValues is no longer valid. Entity has been destroyed.');
|
|
1023
1070
|
}
|
|
1024
|
-
if (_.indexOf(
|
|
1071
|
+
if (_.indexOf(data, this.getIdProperty().name) !== -1) {
|
|
1025
1072
|
throw new Error('Cannot change id via entity.setValues(). Must use entity.setId() first.');
|
|
1026
1073
|
}
|
|
1027
1074
|
|
|
1028
1075
|
let isChanged = false;
|
|
1029
|
-
_.each(
|
|
1076
|
+
_.each(data, (value, propertyName) => {
|
|
1030
1077
|
const property = this.getProperty(propertyName);
|
|
1031
1078
|
property.pauseEvents(); // We don't need property_change to fire
|
|
1032
1079
|
if (property.setValue(value)) {
|
|
@@ -1034,6 +1081,7 @@ class Entity extends EventEmitter {
|
|
|
1034
1081
|
}
|
|
1035
1082
|
property.resumeEvents();
|
|
1036
1083
|
});
|
|
1084
|
+
this.setLastModified();
|
|
1037
1085
|
|
|
1038
1086
|
if (isChanged) {
|
|
1039
1087
|
this._recalculateDependentProperties();
|
package/src/Repository/Ajax.js
CHANGED
|
@@ -362,7 +362,7 @@ class AjaxRepository extends Repository {
|
|
|
362
362
|
throw new Error('No "get" api endpoint defined.');
|
|
363
363
|
}
|
|
364
364
|
this.emit('beforeLoad'); // TODO: canceling beforeLoad will cancel the load operation
|
|
365
|
-
this.
|
|
365
|
+
this.markLoading();
|
|
366
366
|
|
|
367
367
|
|
|
368
368
|
if (!_.isNil(params) && _.isObject(params)) {
|
|
@@ -407,9 +407,9 @@ class AjaxRepository extends Repository {
|
|
|
407
407
|
// Set the total records that pass filter
|
|
408
408
|
this.total = total;
|
|
409
409
|
this._setPaginationVars();
|
|
410
|
-
|
|
411
|
-
this.
|
|
412
|
-
|
|
410
|
+
|
|
411
|
+
this.markLoaded();
|
|
412
|
+
|
|
413
413
|
this.emit('changeData', this.entities);
|
|
414
414
|
this.emit('load', this);
|
|
415
415
|
})
|
|
@@ -432,7 +432,7 @@ class AjaxRepository extends Repository {
|
|
|
432
432
|
throw new Error('No "get" api endpoint defined.');
|
|
433
433
|
}
|
|
434
434
|
this.emit('beforeLoad'); // TODO: canceling beforeLoad will cancel the load operation
|
|
435
|
-
this.
|
|
435
|
+
this.markLoading();
|
|
436
436
|
|
|
437
437
|
const params = this._getReloadEntityParams(entity);
|
|
438
438
|
if (this.debugMode) {
|
|
@@ -460,8 +460,8 @@ class AjaxRepository extends Repository {
|
|
|
460
460
|
entity.loadOriginalData(updatedData, true);
|
|
461
461
|
entity.emit('reload', entity);
|
|
462
462
|
|
|
463
|
-
this.
|
|
464
|
-
|
|
463
|
+
this.markLoaded();
|
|
464
|
+
|
|
465
465
|
this.emit('changeData', this.entities);
|
|
466
466
|
this.emit('load', this);
|
|
467
467
|
this.emit('reloadEntity', entity);
|
package/src/Repository/Memory.js
CHANGED
|
@@ -95,7 +95,7 @@ class MemoryRepository extends Repository {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
this.emit('beforeLoad');
|
|
98
|
-
this.
|
|
98
|
+
this.markLoading();
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
const isDirectLoad = !_.isNil(data);
|
|
@@ -143,8 +143,7 @@ class MemoryRepository extends Repository {
|
|
|
143
143
|
await this._saveToStorage(entities);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
this.
|
|
147
|
-
this.isLoaded = true;
|
|
146
|
+
this.markLoaded();
|
|
148
147
|
this._recalculate(); // fires changeData if needed
|
|
149
148
|
this.emit('load', entities, this);
|
|
150
149
|
return entities;
|
|
@@ -444,6 +443,21 @@ class MemoryRepository extends Repository {
|
|
|
444
443
|
})
|
|
445
444
|
}
|
|
446
445
|
/* */
|
|
446
|
+
|
|
447
|
+
removeEntity(entity) { // standard function notation
|
|
448
|
+
const id = entity.id;
|
|
449
|
+
|
|
450
|
+
super.removeEntity(entity);
|
|
451
|
+
|
|
452
|
+
if (this.hasSorters) {
|
|
453
|
+
this._applySorters();
|
|
454
|
+
}
|
|
455
|
+
if (this.hasFilters) {
|
|
456
|
+
this._applyFilters();
|
|
457
|
+
}
|
|
458
|
+
delete this._keyedEntities[id];
|
|
459
|
+
}
|
|
460
|
+
|
|
447
461
|
|
|
448
462
|
|
|
449
463
|
|
package/src/Repository/Null.js
CHANGED
|
@@ -46,7 +46,7 @@ class NullRepository extends Repository {
|
|
|
46
46
|
throw Error('this.load is no longer valid. Repository has been destroyed.');
|
|
47
47
|
}
|
|
48
48
|
this.emit('beforeLoad');
|
|
49
|
-
this.
|
|
49
|
+
this.markLoading();
|
|
50
50
|
|
|
51
51
|
if (data) {
|
|
52
52
|
let entities = data;
|
|
@@ -65,8 +65,7 @@ class NullRepository extends Repository {
|
|
|
65
65
|
|
|
66
66
|
this._updateSize();
|
|
67
67
|
|
|
68
|
-
this.
|
|
69
|
-
this.isLoaded = true;
|
|
68
|
+
this.markLoaded();
|
|
70
69
|
this.emit('changeData', this.entities);
|
|
71
70
|
this.emit('load', this);
|
|
72
71
|
}
|
|
@@ -5,6 +5,7 @@ import Entity from '../Entity';
|
|
|
5
5
|
import {
|
|
6
6
|
v4 as uuid,
|
|
7
7
|
} from 'uuid';
|
|
8
|
+
import moment from 'moment';
|
|
8
9
|
import _ from 'lodash';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -194,6 +195,11 @@ export default class Repository extends EventEmitter {
|
|
|
194
195
|
*/
|
|
195
196
|
this.isLoading = false;
|
|
196
197
|
|
|
198
|
+
/**
|
|
199
|
+
* @member {string} lastLoaded - Last time this repository was loaded
|
|
200
|
+
*/
|
|
201
|
+
this.lastLoaded = null;
|
|
202
|
+
|
|
197
203
|
/**
|
|
198
204
|
* @member {Boolean} isSaving - State: whether or not entities are currently being saved
|
|
199
205
|
*/
|
|
@@ -268,8 +274,9 @@ export default class Repository extends EventEmitter {
|
|
|
268
274
|
this._createMethods();
|
|
269
275
|
this._createStatics();
|
|
270
276
|
|
|
271
|
-
|
|
272
|
-
|
|
277
|
+
const init = this.schema.repository.init || this.originalConfig.init; // The latter is mainly for lfr repositories
|
|
278
|
+
if (init) {
|
|
279
|
+
await init.call(this);
|
|
273
280
|
}
|
|
274
281
|
|
|
275
282
|
this.isInitialized = true;
|
|
@@ -284,7 +291,7 @@ export default class Repository extends EventEmitter {
|
|
|
284
291
|
if (this.isDestroyed) {
|
|
285
292
|
throw Error('this._createMethods is no longer valid. Repository has been destroyed.');
|
|
286
293
|
}
|
|
287
|
-
const methodDefinitions = this.schema.repository.methods;
|
|
294
|
+
const methodDefinitions = this.schema.repository.methods || this.originalConfig.methods; // The latter is mainly for lfr repositories
|
|
288
295
|
if (!_.isEmpty(methodDefinitions)) {
|
|
289
296
|
_.each(methodDefinitions, (method, name) => {
|
|
290
297
|
this[name] = method; // NOTE: Methods must be defined in schema as "function() {}", not as "() => {}" so "this" will be assigned correctly
|
|
@@ -300,7 +307,7 @@ export default class Repository extends EventEmitter {
|
|
|
300
307
|
if (this.isDestroyed) {
|
|
301
308
|
throw Error('this._createStatics is no longer valid. Entity has been destroyed.');
|
|
302
309
|
}
|
|
303
|
-
const staticsDefinitions = this.schema.repository.statics;
|
|
310
|
+
const staticsDefinitions = this.schema.repository.statics || this.originalConfig.statics; // The latter is mainly for lfr repositories
|
|
304
311
|
if (!_.isEmpty(staticsDefinitions)) {
|
|
305
312
|
_.each(staticsDefinitions, (value, key) => {
|
|
306
313
|
this[key] = value;
|
|
@@ -323,6 +330,22 @@ export default class Repository extends EventEmitter {
|
|
|
323
330
|
throw new Error('load must be implemented by Repository subclass');
|
|
324
331
|
}
|
|
325
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Marks this repository as loading
|
|
335
|
+
*/
|
|
336
|
+
markLoading = () => {
|
|
337
|
+
this.isLoading = true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Marks this repository as loaded
|
|
342
|
+
*/
|
|
343
|
+
markLoaded = () => {
|
|
344
|
+
this.isLoading = false;
|
|
345
|
+
this.isLoaded = true;
|
|
346
|
+
this.lastLoaded = moment(new Date()).format('YYYY-MM-DD HH:mm:ss.SSSS');
|
|
347
|
+
}
|
|
348
|
+
|
|
326
349
|
/**
|
|
327
350
|
* Reload data from storage medium, using previous settings.
|
|
328
351
|
* Subclasses may override this to provide additional
|
|
@@ -900,6 +923,19 @@ export default class Repository extends EventEmitter {
|
|
|
900
923
|
throw Error('this.add is no longer valid. Repository has been destroyed.');
|
|
901
924
|
}
|
|
902
925
|
|
|
926
|
+
// Does it already exist? If so, edit the existing
|
|
927
|
+
const idProperty = this.getSchema().model.idProperty;
|
|
928
|
+
if (data.hasOwnProperty(idProperty)) {
|
|
929
|
+
if (this.isInRepository(data[idProperty])) {
|
|
930
|
+
const existing = this.getById(data[idProperty]);
|
|
931
|
+
existing.setRawValues(data);
|
|
932
|
+
if (this.autoSave && !existing.isPersisted) {
|
|
933
|
+
await this.save(existing);
|
|
934
|
+
}
|
|
935
|
+
return existing;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
903
939
|
let entity = data;
|
|
904
940
|
if (!(data instanceof Entity)) {
|
|
905
941
|
// Create the new entity
|
|
@@ -1595,7 +1631,7 @@ export default class Repository extends EventEmitter {
|
|
|
1595
1631
|
* Mainly used for phantom Entities
|
|
1596
1632
|
* Helper for delete()
|
|
1597
1633
|
*/
|
|
1598
|
-
removeEntity
|
|
1634
|
+
removeEntity(entity) { // standard function notation so it can be called by child class
|
|
1599
1635
|
this.entities = _.filter(this.entities, e => e !== entity);
|
|
1600
1636
|
entity.destroy();
|
|
1601
1637
|
}
|