@onehat/data 1.7.12 → 1.7.16

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.
@@ -580,6 +580,21 @@ describe('Entity', function() {
580
580
  this.entity.markStaged(false);
581
581
  expect(this.entity.isStaged).to.be.false;
582
582
  });
583
+
584
+ it('setValue changed lastModified', function() {
585
+ let earlyLastModified,
586
+ lateLastModified;
587
+
588
+ earlyLastModified = this.entity.lastModified;
589
+ this.entity.setValue('foo', '125');
590
+ lateLastModified = this.entity.lastModified;
591
+ expect(earlyLastModified < lateLastModified).to.be.true;
592
+
593
+ earlyLastModified = this.entity.lastModified;
594
+ this.entity.foo = '126';
595
+ lateLastModified = this.entity.lastModified;
596
+ expect(earlyLastModified < lateLastModified).to.be.true;
597
+ });
583
598
  });
584
599
 
585
600
  describe('events', function() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/data",
3
- "version": "1.7.12",
3
+ "version": "1.7.16",
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
@@ -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
  }
@@ -1034,6 +1046,7 @@ class Entity extends EventEmitter {
1034
1046
  }
1035
1047
  property.resumeEvents();
1036
1048
  });
1049
+ this.setLastModified();
1037
1050
 
1038
1051
  if (isChanged) {
1039
1052
  this._recalculateDependentProperties();
@@ -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.isLoading = true;
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.isLoading = false;
412
- this.isLoaded = true;
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.isLoading = true;
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.isLoading = false;
464
- this.isLoaded = true;
463
+ this.markLoaded();
464
+
465
465
  this.emit('changeData', this.entities);
466
466
  this.emit('load', this);
467
467
  this.emit('reloadEntity', entity);
@@ -95,7 +95,7 @@ class MemoryRepository extends Repository {
95
95
  }
96
96
 
97
97
  this.emit('beforeLoad');
98
- this.isLoading = true;
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.isLoading = false;
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
 
@@ -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.isLoading = true;
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.isLoading = false;
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
  */
@@ -266,9 +272,11 @@ export default class Repository extends EventEmitter {
266
272
  }
267
273
 
268
274
  this._createMethods();
275
+ this._createStatics();
269
276
 
270
- if (this.schema.repository.init) {
271
- await this.schema.repository.init.call(this);
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);
272
280
  }
273
281
 
274
282
  this.isInitialized = true;
@@ -283,7 +291,7 @@ export default class Repository extends EventEmitter {
283
291
  if (this.isDestroyed) {
284
292
  throw Error('this._createMethods is no longer valid. Repository has been destroyed.');
285
293
  }
286
- const methodDefinitions = this.schema.repository.methods;
294
+ const methodDefinitions = this.schema.repository.methods || this.originalConfig.methods; // The latter is mainly for lfr repositories
287
295
  if (!_.isEmpty(methodDefinitions)) {
288
296
  _.each(methodDefinitions, (method, name) => {
289
297
  this[name] = method; // NOTE: Methods must be defined in schema as "function() {}", not as "() => {}" so "this" will be assigned correctly
@@ -291,6 +299,22 @@ export default class Repository extends EventEmitter {
291
299
  }
292
300
  }
293
301
 
302
+ /**
303
+ * Creates the static properties for this Repository, based on Schema.
304
+ * @private
305
+ */
306
+ _createStatics = () => {
307
+ if (this.isDestroyed) {
308
+ throw Error('this._createStatics is no longer valid. Entity has been destroyed.');
309
+ }
310
+ const staticsDefinitions = this.schema.repository.statics || this.originalConfig.statics; // The latter is mainly for lfr repositories
311
+ if (!_.isEmpty(staticsDefinitions)) {
312
+ _.each(staticsDefinitions, (value, key) => {
313
+ this[key] = value;
314
+ });
315
+ }
316
+ }
317
+
294
318
 
295
319
  // __ __
296
320
  // / / ____ ____ _____/ /
@@ -306,6 +330,22 @@ export default class Repository extends EventEmitter {
306
330
  throw new Error('load must be implemented by Repository subclass');
307
331
  }
308
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
+
309
349
  /**
310
350
  * Reload data from storage medium, using previous settings.
311
351
  * Subclasses may override this to provide additional
@@ -1255,49 +1295,6 @@ export default class Repository extends EventEmitter {
1255
1295
  return associatedRepository;
1256
1296
  }
1257
1297
 
1258
- /**
1259
- * Gets the Schema object
1260
- * @return {Schema} schema
1261
- */
1262
- getSchema = () => {
1263
- if (this.isDestroyed) {
1264
- throw Error('this.getSchema is no longer valid. Entity has been destroyed.');
1265
- }
1266
- return this.schema;
1267
- }
1268
-
1269
- /**
1270
- * Gets the associated Repository
1271
- * @param {string} repositoryName - Name of the Repository to retrieve
1272
- * @return {boolean} hasProperty
1273
- */
1274
- getAssociatedRepository = (repositoryName) => {
1275
- if (this.isDestroyed) {
1276
- throw Error('this.getAssociatedRepository is no longer valid. Entity has been destroyed.');
1277
- }
1278
-
1279
- const schema = this.getSchema();
1280
- if (!schema.model.associations.hasOne.includes(repositoryName) &&
1281
- !schema.model.associations.hasMany.includes(repositoryName) &&
1282
- !schema.model.associations.belongsTo.includes(repositoryName) &&
1283
- !schema.model.associations.belongsToMany.includes(repositoryName)
1284
- ) {
1285
- throw Error(repositoryName + ' is not associated with this schema');
1286
- }
1287
-
1288
- const oneHatData = this.oneHatData;
1289
- if (!oneHatData) {
1290
- throw Error('No global oneHatData object');
1291
- }
1292
-
1293
- const associatedRepository = oneHatData.getRepository(repositoryName);
1294
- if (!associatedRepository) {
1295
- throw Error('Repository ' + repositoryName + ' cannot be found');
1296
- }
1297
-
1298
- return associatedRepository;
1299
- }
1300
-
1301
1298
  /**
1302
1299
  * Utility function.
1303
1300
  * Detects if entity is in the current page of the storage medium.
@@ -1621,7 +1618,7 @@ export default class Repository extends EventEmitter {
1621
1618
  * Mainly used for phantom Entities
1622
1619
  * Helper for delete()
1623
1620
  */
1624
- removeEntity = async (entity) => {
1621
+ removeEntity(entity) { // standard function notation so it can be called by child class
1625
1622
  this.entities = _.filter(this.entities, e => e !== entity);
1626
1623
  entity.destroy();
1627
1624
  }