@onehat/data 1.6.1 → 1.6.6

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.
@@ -25,6 +25,21 @@ const GroupsUsers = {
25
25
  { name: 'users__last_login', mapping: 'user.last_login', type: 'datetime' },
26
26
  ],
27
27
 
28
+ associations: {
29
+ hasOne: [
30
+ 'Groups',
31
+ 'Users',
32
+ ],
33
+ },
34
+
35
+ },
36
+
37
+ entity: {
38
+ methods: {
39
+ testMethod: function() {
40
+ this.users__login_count = this.users__login_count +1;
41
+ },
42
+ },
28
43
  },
29
44
 
30
45
  repository: 'onebuild',
@@ -17,6 +17,13 @@ describe('Entity', function() {
17
17
  { name: 'baz', mapping: 'baz.test.val', type: 'bool', defaultValue: null, },
18
18
  ],
19
19
  },
20
+ entity: {
21
+ methods: {
22
+ testMethod: function() {
23
+ this.bar = 'test me';
24
+ },
25
+ },
26
+ },
20
27
  });
21
28
  this.data = {
22
29
  foo: 1,
@@ -71,7 +78,6 @@ describe('Entity', function() {
71
78
  entity.markSaved();
72
79
  expect(entity.isTempId).to.be.false;
73
80
  });
74
-
75
81
 
76
82
  it('clone', function() {
77
83
  const entity = this.entity;
@@ -100,6 +106,11 @@ describe('Entity', function() {
100
106
  expect(entity.properties.bar.name).to.be.eq('bar');
101
107
  expect(entity.properties.baz.name).to.be.eq('baz');
102
108
  });
109
+
110
+ it('_createMethods', function() {
111
+ this.entity.testMethod();
112
+ expect(this.entity.bar).to.be.eq('test me');
113
+ });
103
114
 
104
115
  it('loadOriginalData', function() {
105
116
  const data = {
@@ -175,6 +175,21 @@ describe('OneHatData', function() {
175
175
  })();
176
176
  });
177
177
 
178
+ it('hasRepository', function() {
179
+ (async function() {
180
+ await beforeEach();
181
+
182
+ const oneHatData = new OneHatData();
183
+ oneHatData.createSchemas([
184
+ { name: 'foo', },
185
+ ]);
186
+ oneHatData.createBoundRepositories();
187
+ expect(oneHatData.hasRepository('foo')).to.be.true;
188
+
189
+ afterEach();
190
+ })();
191
+ });
192
+
178
193
  it('hasRepositoryWithId', function() {
179
194
  (async function() {
180
195
  await beforeEach();
@@ -685,6 +685,11 @@ describe('Repository Base', function() {
685
685
 
686
686
  describe('deleting', function() {
687
687
 
688
+ it('clear', function() {
689
+ this.repository.clear();
690
+ expect(this.repository.entities.length).to.be.eq(0);
691
+ });
692
+
688
693
  it('delete', function() {
689
694
  let didFire = false;
690
695
  this.repository.on('delete', () => {
@@ -698,6 +703,13 @@ describe('Repository Base', function() {
698
703
  expect(didFire).to.be.true;
699
704
  });
700
705
 
706
+ it('delete() / removeEntity', async function() {
707
+ this.repository.setAutoSave(false);
708
+ const entity = await this.repository.add({ value: 'six' });
709
+ this.repository.delete(entity);
710
+ expect(this.repository.entities.length).to.be.eq(5);
711
+ });
712
+
701
713
 
702
714
  // I'm going to skip these, as they're just combinations of other, tested functions
703
715
  // deleteByIx
@@ -10,6 +10,11 @@ describe('Schema', function() {
10
10
  it('schema is valid', function() {
11
11
  expect(this.schema instanceof Schema).to.be.true;
12
12
  expect(this.schema.name).to.be.eq('GroupsUsers');
13
+ expect(this.schema.model.associations.hasOne).to.be.an('array');
14
+ expect(this.schema.model.associations.hasMany).to.be.an('array');
15
+ expect(this.schema.model.associations.belongsTo).to.be.an('array');
16
+ expect(this.schema.model.associations.belongsToMany).to.be.an('array');
17
+ expect(this.schema.entity.methods.testMethod).to.be.a('function');
13
18
  expect(this.schema.repository.type).to.be.eq('onebuild');
14
19
  });
15
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/data",
3
- "version": "1.6.1",
3
+ "version": "1.6.6",
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
@@ -150,10 +150,28 @@ class Entity extends EventEmitter {
150
150
 
151
151
  initialize = () => {
152
152
  this.properties = this._createProperties();
153
+ this._createMethods();
153
154
  this.reset();
154
155
  this.isInitialized = true;
155
156
  }
156
157
 
158
+ /**
159
+ * Creates the methods for this Entity, based on Schema.
160
+ * @private
161
+ */
162
+ _createMethods = () => {
163
+ if (this.isDestroyed) {
164
+ throw Error('this._createMethods is no longer valid. Entity has been destroyed.');
165
+ }
166
+ const methodDefinitions = this.schema.entity.methods;
167
+ if (_.isEmpty(methodDefinitions)) {
168
+ return;
169
+ }
170
+ _.each(methodDefinitions, (method, name) => {
171
+ this[name] = method; // NOTE: Methods must be defined in schema as "function() {}", not as "() => {}" so "this" will be assigned correctly
172
+ });
173
+ }
174
+
157
175
  /**
158
176
  * Generates a new unique id and assigns it to this entity.
159
177
  */
@@ -380,6 +398,17 @@ class Entity extends EventEmitter {
380
398
  return this.schema;
381
399
  }
382
400
 
401
+ /**
402
+ * Gets the Repository object
403
+ * @return {Repository} repository
404
+ */
405
+ getRepository = () => {
406
+ if (this.isDestroyed) {
407
+ throw Error('this.getRepository is no longer valid. Entity has been destroyed.');
408
+ }
409
+ return this.repository;
410
+ }
411
+
383
412
  /**
384
413
  * Alias for this.properties
385
414
  */
@@ -833,6 +862,43 @@ class Entity extends EventEmitter {
833
862
  return this._originalData;
834
863
  }
835
864
 
865
+ /**
866
+ * Gets the associated Repository
867
+ * @param {string} repositoryName - Name of the Repository to retrieve
868
+ * @return {boolean} hasProperty
869
+ */
870
+ getAssociatedRepository = (repositoryName) => {
871
+ if (this.isDestroyed) {
872
+ throw Error('this.getAssociatedRepository is no longer valid. Entity has been destroyed.');
873
+ }
874
+
875
+ const schema = this.getSchema();
876
+ if (!schema.model.associations.hasOne.includes(repositoryName) &&
877
+ !schema.model.associations.hasMany.includes(repositoryName) &&
878
+ !schema.model.associations.belongsTo.includes(repositoryName) &&
879
+ !schema.model.associations.belongsToMany.includes(repositoryName)
880
+ ) {
881
+ throw Error(repositoryName + ' is not associated with this schema');
882
+ }
883
+
884
+ const repository = this.getRepository();
885
+ if (!repository) {
886
+ throw Error('No repository on this entity');
887
+ }
888
+
889
+ const oneHatData = repository.oneHatData;
890
+ if (!oneHatData) {
891
+ throw Error('No global oneHatData object');
892
+ }
893
+
894
+ const associatedRepository = oneHatData.getRepository(repositoryName);
895
+ if (!associatedRepository) {
896
+ throw Error('Repository ' + repositoryName + ' cannot be found');
897
+ }
898
+
899
+ return associatedRepository;
900
+ }
901
+
836
902
 
837
903
 
838
904
  // _____ __ __
package/src/OneHatData.js CHANGED
@@ -254,8 +254,8 @@ export class OneHatData extends EventEmitter {
254
254
  }
255
255
 
256
256
  // Apply the general config settings to each specific one
257
- const localConfig = _.assign({}, generalConfig, config.local),
258
- remoteConfig = _.assign({}, generalConfig, config.remote);
257
+ const localConfig = _.merge({}, generalConfig, config.local),
258
+ remoteConfig = _.merge({}, generalConfig, config.remote);
259
259
 
260
260
  // Actually create the local and remote repositories
261
261
  config.local = await this.createRepository(localConfig);
@@ -263,7 +263,7 @@ export class OneHatData extends EventEmitter {
263
263
  }
264
264
 
265
265
  const Repository = this._repositoryTypes[config.type],
266
- repository = new Repository(config);
266
+ repository = new Repository(config, this);
267
267
  await repository.initialize();
268
268
 
269
269
  return repository;
@@ -442,6 +442,19 @@ export class OneHatData extends EventEmitter {
442
442
  return schema.getBoundRepository();
443
443
  }
444
444
 
445
+ /**
446
+ * Checks whether the requested bound Repository exists.
447
+ * @param {string} name - Name of Schema
448
+ * @return {boolean} hasRepository
449
+ */
450
+ hasRepository = (name) => {
451
+ if (this.isDestroyed) {
452
+ throw new Error('this.getRepository is no longer valid. OneHatData has been destroyed.');
453
+ }
454
+ const repository = this.getRepository(name);
455
+ return !!repository;
456
+ }
457
+
445
458
  /**
446
459
  * Get Repositories by a filter function
447
460
  * @param {function} filter - Filter function
@@ -370,7 +370,7 @@ class AjaxRepository extends Repository {
370
370
  }
371
371
 
372
372
  const repository = this;
373
- const data = _.assign({}, this._baseParams, this._params);
373
+ const data = _.merge({}, this._baseParams, this._params);
374
374
 
375
375
  return this._send(this.methods.get, this.api.get, data)
376
376
  .then(result => {
@@ -678,15 +678,6 @@ class AjaxRepository extends Repository {
678
678
  }));
679
679
  }
680
680
 
681
- /**
682
- * Deletes all locally cached entities in repository,
683
- * usually, the current "page".
684
- * Does not actually affect anything on the server.
685
- */
686
- clear = async () => {
687
- this._destroyEntities();
688
- }
689
-
690
681
  }
691
682
 
692
683
  AjaxRepository.className = 'Ajax';
@@ -23,7 +23,7 @@ export default class Repository extends EventEmitter {
23
23
  * - name {string} - Optional. Defaults to schema.name
24
24
  * - schema - Schema object
25
25
  */
26
- constructor(config = {}) {
26
+ constructor(config = {}, oneHatData = null) {
27
27
  super(...arguments);
28
28
 
29
29
  const { schema } = config;
@@ -214,6 +214,11 @@ export default class Repository extends EventEmitter {
214
214
  */
215
215
  this.isDestroyed = false;
216
216
 
217
+ /**
218
+ * @member {boolean} oneHatData - The global @onehat/data object
219
+ */
220
+ this.oneHatData = oneHatData;
221
+
217
222
  this.registerEvents([
218
223
  'add',
219
224
  'beforeSave',
@@ -972,6 +977,15 @@ export default class Repository extends EventEmitter {
972
977
  this.entities = [];
973
978
  }
974
979
 
980
+ /**
981
+ * Deletes all locally cached entities in repository,
982
+ * usually, the current "page".
983
+ * Does not actually affect anything on the server.
984
+ */
985
+ clear = async () => {
986
+ this._destroyEntities();
987
+ }
988
+
975
989
  /**
976
990
  * Gets an array of "submit" values objects for the entities
977
991
  * @return {array} map -
@@ -1365,7 +1379,7 @@ export default class Repository extends EventEmitter {
1365
1379
 
1366
1380
  /**
1367
1381
  * Marks entities for deletion from storage medium.
1368
- * Actual deletion takes place in save()
1382
+ * Actual deletion takes place in save(), unless isPhantom
1369
1383
  * @param {object|array} entities - one or more entities to delete
1370
1384
  * @fires delete
1371
1385
  */
@@ -1381,7 +1395,12 @@ export default class Repository extends EventEmitter {
1381
1395
  return;
1382
1396
  }
1383
1397
  _.each(entities, (entity) => {
1384
- entity.markDeleted(); // Entity is still there, it's just marked for deletion
1398
+ if (entity.isPhantom) {
1399
+ // Just auto-remove it. Don't bother saving to storage medium.
1400
+ this.removeEntity(entity);
1401
+ } else {
1402
+ entity.markDeleted(); // Entity is still there, it's just marked for deletion
1403
+ }
1385
1404
  });
1386
1405
 
1387
1406
  this.emit('delete', entities);
@@ -1391,6 +1410,16 @@ export default class Repository extends EventEmitter {
1391
1410
  }
1392
1411
  }
1393
1412
 
1413
+ /**
1414
+ * Removes an Entity from the current page
1415
+ * Mainly used for phantom Entities
1416
+ * Helper for delete()
1417
+ */
1418
+ removeEntity = async (entity) => {
1419
+ this.entities = _.filter(this.entities, e => e !== entity);
1420
+ entity.destroy();
1421
+ }
1422
+
1394
1423
  /**
1395
1424
  * Deletes a single Entity by its index (zero-indexed) on the current page
1396
1425
  * @param {integer} ix - Index
@@ -5,11 +5,12 @@ import _ from 'lodash';
5
5
 
6
6
  /**
7
7
  * Class represents the Schema definition for Model and Source
8
- * This is basically just a big config object, used to instantiate a Model and Source.
8
+ * This is basically just a big config object, used to instantiate an Entity, and Repository.
9
9
  * Usage:
10
10
  * - const schema = new Schema({
11
11
  * name: 'Users',
12
12
  * model: {},
13
+ * entity: {},
13
14
  * repository: {},
14
15
  * });
15
16
  *
@@ -96,6 +97,10 @@ export default class Schema extends EventEmitter {
96
97
  belongsToMany: [],
97
98
  },
98
99
  },
100
+
101
+ entity: {
102
+ methods: {}, // NOTE: Methods must be defined as "function() {}", not as "() => {}" so "this" will be assigned correctly
103
+ },
99
104
 
100
105
  /**
101
106
  * @member {object|string} repository - Config for Repository
@@ -104,7 +109,7 @@ export default class Schema extends EventEmitter {
104
109
 
105
110
  };
106
111
 
107
- this._originalConfig = _.assign({}, defaults, config);
112
+ this._originalConfig = _.merge({}, defaults, config);
108
113
  _.merge(this, this._originalConfig);
109
114
 
110
115
  /**