@onehat/data 1.7.2 → 1.7.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.
@@ -77,6 +77,9 @@ describe('Entity', function() {
77
77
 
78
78
  entity.markSaved();
79
79
  expect(entity.isTempId).to.be.false;
80
+
81
+ entity.isTempId = true;
82
+ expect(entity.isTempId).to.be.true;
80
83
  });
81
84
 
82
85
  it('clone', function() {
@@ -522,6 +525,7 @@ describe('Entity', function() {
522
525
  entity.bar = 'Test';
523
526
  entity.markSaved();
524
527
  expect(entity.isTempId).to.be.false;
528
+ expect(entity.isStaged).to.be.false;
525
529
 
526
530
  expect(entity.isPersisted).to.be.true;
527
531
  const expected = {
@@ -554,6 +558,16 @@ describe('Entity', function() {
554
558
  this.entity.undelete();
555
559
  expect(this.entity.isDeleted).to.be.false;
556
560
  });
561
+
562
+ it('markStaged, stage', function() {
563
+ expect(this.entity.isStaged).to.be.false;
564
+
565
+ this.entity.stage();
566
+ expect(this.entity.isStaged).to.be.true;
567
+
568
+ this.entity.markStaged(false);
569
+ expect(this.entity.isStaged).to.be.false;
570
+ });
557
571
  });
558
572
 
559
573
  describe('events', function() {
@@ -12,6 +12,11 @@ describe('Repository Base', function() {
12
12
  { name: 'key', type: 'int' },
13
13
  { name: 'value' },
14
14
  ],
15
+ associations: {
16
+ hasMany: [
17
+ 'bar'
18
+ ],
19
+ },
15
20
  },
16
21
  repository: 'null',
17
22
  });
@@ -63,6 +68,43 @@ describe('Repository Base', function() {
63
68
  })
64
69
  expect(repository.id).to.be.not.eq('foo');
65
70
  });
71
+
72
+ it('_createMethods', async function() {
73
+
74
+ // There is no test method by default
75
+ expect(this.repository.testMethod).to.not.exist;
76
+
77
+ // Set up custom repository with testMethod
78
+ const schema1 = new Schema({
79
+ name: 'baz',
80
+ model: {
81
+ idProperty: 'key',
82
+ displayProperty: 'value',
83
+ properties: [
84
+ { name: 'key', type: 'int' },
85
+ { name: 'value' },
86
+ ],
87
+ },
88
+ repository: {
89
+ type: 'null',
90
+ methods: {
91
+ testMethod: function() {
92
+ this.bar = 'test me';
93
+ },
94
+ },
95
+ },
96
+ }),
97
+ repository = new this.Repository({
98
+ schema: schema1,
99
+ data: [],
100
+ });
101
+ await repository.initialize();
102
+ schema1.setBoundRepository(repository);
103
+
104
+ // Perform testMethod test
105
+ repository.testMethod();
106
+ expect(repository.bar).to.be.eq('test me');
107
+ });
66
108
  });
67
109
 
68
110
  describe('loading', function() {
@@ -608,8 +650,12 @@ describe('Repository Base', function() {
608
650
  it('getNonPersisted', async function() {
609
651
  this.repository.setAutoSave(false);
610
652
  const entity = await this.repository.add({ value: 'six' }),
611
- nonPersisted = this.repository.getNonPersisted();
653
+ entities = this.repository.entities;
612
654
 
655
+ let nonPersisted = this.repository.getNonPersisted();
656
+ expect(_.isEqual(entity, nonPersisted[0])).to.be.true;
657
+
658
+ nonPersisted = this.repository.getNonPersisted(entities);
613
659
  expect(_.isEqual(entity, nonPersisted[0])).to.be.true;
614
660
  });
615
661
 
@@ -648,6 +694,15 @@ describe('Repository Base', function() {
648
694
  expect(_.isEqual(entity, deleted[0])).to.be.true;
649
695
  });
650
696
 
697
+ it('getStaged', function() {
698
+ this.repository.setAutoSave(false);
699
+ const entity = this.repository.getByIx(0);
700
+
701
+ entity.isStaged = true;
702
+ const staged = this.repository.getStaged();
703
+ expect(_.isEqual(entity, staged[0])).to.be.true;
704
+ });
705
+
651
706
  it('isInRepository, hasId', function() {
652
707
  this.repository.setAutoSave(false);
653
708
  const id = 1,
@@ -678,6 +733,9 @@ describe('Repository Base', function() {
678
733
  this.repository.add({ value: 'six' });
679
734
 
680
735
  expect(didFire).to.be.true;
736
+
737
+ // TODO: Need comprehensive test of save(), not just event firing
738
+
681
739
  });
682
740
 
683
741
  it('save by entity', async function() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/data",
3
- "version": "1.7.2",
3
+ "version": "1.7.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
@@ -117,6 +117,12 @@ class Entity extends EventEmitter {
117
117
  */
118
118
  this.isDeleted = false;
119
119
 
120
+ /**
121
+ * @member {boolean} isStaged - Whether this object has been marked for saving
122
+ * @private
123
+ */
124
+ this.isStaged = false;
125
+
120
126
  /**
121
127
  * @member {boolean} isDestroyed - Whether this object has been destroyed
122
128
  * @private
@@ -165,12 +171,11 @@ class Entity extends EventEmitter {
165
171
  throw Error('this._createMethods is no longer valid. Entity has been destroyed.');
166
172
  }
167
173
  const methodDefinitions = this.schema.entity.methods;
168
- if (_.isEmpty(methodDefinitions)) {
169
- return;
174
+ if (!_.isEmpty(methodDefinitions)) {
175
+ _.each(methodDefinitions, (method, name) => {
176
+ this[name] = method; // NOTE: Methods must be defined in schema as "function() {}", not as "() => {}" so "this" will be assigned correctly
177
+ });
170
178
  }
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
179
  }
175
180
 
176
181
  /**
@@ -774,6 +779,14 @@ class Entity extends EventEmitter {
774
779
  return this.getIdProperty().isTempId;
775
780
  }
776
781
 
782
+ /**
783
+ * Marks this Entity's idProperty's isTempId field
784
+ * @return {boolean} isTempId
785
+ */
786
+ set isTempId(bool) {
787
+ this.getIdProperty().isTempId = bool;
788
+ }
789
+
777
790
  /**
778
791
  * Gets the "Display" Property object for this Entity.
779
792
  * This is the Property whose value can easily identify the whole Entity itself.
@@ -1050,6 +1063,7 @@ class Entity extends EventEmitter {
1050
1063
  this.getIdProperty().isTempId = false;
1051
1064
  this._originalData = this._getReconstructedOriginalData();
1052
1065
  this._originalDataParsed = this.getParsedValues();
1066
+ this.markStaged(false);
1053
1067
  }
1054
1068
 
1055
1069
  /**
@@ -1095,6 +1109,24 @@ class Entity extends EventEmitter {
1095
1109
  this.emit('undelete', this._proxy);
1096
1110
  }
1097
1111
 
1112
+ /**
1113
+ * Marks an entity as having been staged for saving.
1114
+ * @param {boolean} bool - How it should be marked. Defaults to true.
1115
+ */
1116
+ markStaged = (bool = true) => {
1117
+ if (this.isDestroyed) {
1118
+ throw Error('this.markStaged is no longer valid. Entity has been destroyed.');
1119
+ }
1120
+ this.isStaged = bool;
1121
+ }
1122
+
1123
+ /**
1124
+ * Convenience function.
1125
+ */
1126
+ stage = () => {
1127
+ this.markStaged(true);
1128
+ }
1129
+
1098
1130
  /**
1099
1131
  * Destroy this object.
1100
1132
  * - Removes all circular references to parent objects
@@ -238,9 +238,10 @@ class AjaxRepository extends Repository {
238
238
  matches = name.match(re),
239
239
  paramsToChange = isBaseParam ? this._baseParams : this._params;
240
240
 
241
+ let first, second;
241
242
  if (matches) { // name has array notation like 'conditions[username]'
242
- const first = matches[1],
243
- second = matches[2];
243
+ first = matches[1],
244
+ second = matches[2];
244
245
  if (paramsToChange && !paramsToChange.hasOwnProperty(first)) {
245
246
  paramsToChange[first] = [];
246
247
  }
@@ -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
  // / / ____ ____ _____/ /
@@ -1117,52 +1135,163 @@ export default class Repository extends EventEmitter {
1117
1135
 
1118
1136
  /**
1119
1137
  * Get all phantom (unsaved) Entities
1138
+ * @param {array} entities - Array of entities to filter. Optional. Defaults to this.entities
1120
1139
  * @return {Entity[]} Array of phantom Entities, or []
1121
1140
  */
1122
- getPhantom = () => {
1141
+ getPhantom = (entities = null) => {
1123
1142
  if (this.isDestroyed) {
1124
1143
  throw Error('this.getPhantom is no longer valid. Repository has been destroyed.');
1125
1144
  }
1126
- return _.filter(this.entities, (entity) => entity.isPhantom);
1145
+ if (!entities) {
1146
+ entities = this.entities;
1147
+ }
1148
+ return _.filter(this.entities, entity => entity.isPhantom);
1127
1149
  }
1128
1150
 
1129
1151
  /**
1130
1152
  * Get all Entities not yet persisted to a storage medium
1153
+ * @param {array} entities - Array of entities to filter. Optional. Defaults to this.entities
1131
1154
  * @return {Entity[]} Array of dirty Entities, or []
1132
1155
  */
1133
- getNonPersisted = () => {
1156
+ getNonPersisted = (entities = null) => {
1134
1157
  if (this.isDestroyed) {
1135
1158
  throw Error('this.getDirty is no longer valid. Repository has been destroyed.');
1136
1159
  }
1137
- return _.filter(this.entities, (entity) => {
1138
- return !entity.isPersisted;
1139
- });
1160
+ if (!entities) {
1161
+ entities = this.entities;
1162
+ }
1163
+ return _.filter(this.entities, entity => !entity.isPersisted);
1140
1164
  }
1141
1165
 
1142
1166
  /**
1143
1167
  * Get all dirty (having unsaved changes) Entities
1168
+ * @param {array} entities - Array of entities to filter. Optional. Defaults to this.entities
1144
1169
  * @return {Entity[]} Array of dirty Entities, or []
1145
1170
  */
1146
- getDirty = () => {
1171
+ getDirty = (entities = null) => {
1147
1172
  if (this.isDestroyed) {
1148
1173
  throw Error('this.getDirty is no longer valid. Repository has been destroyed.');
1149
1174
  }
1150
- return _.filter(this.entities, (entity) => {
1151
- return entity.isDirty;
1152
- });
1175
+ if (!entities) {
1176
+ entities = this.entities;
1177
+ }
1178
+ return _.filter(entities, entity => entity.isDirty);
1153
1179
  }
1154
1180
 
1155
1181
  /**
1156
1182
  * Get all deleted Entities
1183
+ * @param {array} entities - Array of entities to filter. Optional. Defaults to this.entities
1157
1184
  * @return {Entity[]} Array of deleted Entities, or []
1158
1185
  */
1159
- getDeleted = () => {
1186
+ getDeleted = (entities = null) => {
1160
1187
  if (this.isDestroyed) {
1161
1188
  throw Error('this.getDeleted is no longer valid. Repository has been destroyed.');
1162
1189
  }
1163
- return _.filter(this.entities, (entity) => {
1164
- return entity.isDeleted;
1165
- });
1190
+ if (!entities) {
1191
+ entities = this.entities;
1192
+ }
1193
+ return _.filter(entities, entity => entity.isDeleted);
1194
+ }
1195
+
1196
+ /**
1197
+ * Get all staged Entities
1198
+ * @param {array} entities - Array of entities to filter. Optional. Defaults to this.entities
1199
+ * @return {Entity[]} Array of deleted Entities, or []
1200
+ */
1201
+ getStaged = (entities = null) => {
1202
+ if (this.isDestroyed) {
1203
+ throw Error('this.getStaged is no longer valid. Repository has been destroyed.');
1204
+ }
1205
+ if (!entities) {
1206
+ entities = this.entities;
1207
+ }
1208
+ return _.filter(entities, entity => entity.isStaged);
1209
+ }
1210
+
1211
+ /**
1212
+ * Gets the Schema object
1213
+ * @return {Schema} schema
1214
+ */
1215
+ getSchema = () => {
1216
+ if (this.isDestroyed) {
1217
+ throw Error('this.getSchema is no longer valid. Entity has been destroyed.');
1218
+ }
1219
+ return this.schema;
1220
+ }
1221
+
1222
+ /**
1223
+ * Gets the associated Repository
1224
+ * @param {string} repositoryName - Name of the Repository to retrieve
1225
+ * @return {boolean} hasProperty
1226
+ */
1227
+ getAssociatedRepository = (repositoryName) => {
1228
+ if (this.isDestroyed) {
1229
+ throw Error('this.getAssociatedRepository is no longer valid. Entity has been destroyed.');
1230
+ }
1231
+
1232
+ const schema = this.getSchema();
1233
+ if (!schema.model.associations.hasOne.includes(repositoryName) &&
1234
+ !schema.model.associations.hasMany.includes(repositoryName) &&
1235
+ !schema.model.associations.belongsTo.includes(repositoryName) &&
1236
+ !schema.model.associations.belongsToMany.includes(repositoryName)
1237
+ ) {
1238
+ throw Error(repositoryName + ' is not associated with this schema');
1239
+ }
1240
+
1241
+ const oneHatData = this.oneHatData;
1242
+ if (!oneHatData) {
1243
+ throw Error('No global oneHatData object');
1244
+ }
1245
+
1246
+ const associatedRepository = oneHatData.getRepository(repositoryName);
1247
+ if (!associatedRepository) {
1248
+ throw Error('Repository ' + repositoryName + ' cannot be found');
1249
+ }
1250
+
1251
+ return associatedRepository;
1252
+ }
1253
+
1254
+ /**
1255
+ * Gets the Schema object
1256
+ * @return {Schema} schema
1257
+ */
1258
+ getSchema = () => {
1259
+ if (this.isDestroyed) {
1260
+ throw Error('this.getSchema is no longer valid. Entity has been destroyed.');
1261
+ }
1262
+ return this.schema;
1263
+ }
1264
+
1265
+ /**
1266
+ * Gets the associated Repository
1267
+ * @param {string} repositoryName - Name of the Repository to retrieve
1268
+ * @return {boolean} hasProperty
1269
+ */
1270
+ getAssociatedRepository = (repositoryName) => {
1271
+ if (this.isDestroyed) {
1272
+ throw Error('this.getAssociatedRepository is no longer valid. Entity has been destroyed.');
1273
+ }
1274
+
1275
+ const schema = this.getSchema();
1276
+ if (!schema.model.associations.hasOne.includes(repositoryName) &&
1277
+ !schema.model.associations.hasMany.includes(repositoryName) &&
1278
+ !schema.model.associations.belongsTo.includes(repositoryName) &&
1279
+ !schema.model.associations.belongsToMany.includes(repositoryName)
1280
+ ) {
1281
+ throw Error(repositoryName + ' is not associated with this schema');
1282
+ }
1283
+
1284
+ const oneHatData = this.oneHatData;
1285
+ if (!oneHatData) {
1286
+ throw Error('No global oneHatData object');
1287
+ }
1288
+
1289
+ const associatedRepository = oneHatData.getRepository(repositoryName);
1290
+ if (!associatedRepository) {
1291
+ throw Error('Repository ' + repositoryName + ' cannot be found');
1292
+ }
1293
+
1294
+ return associatedRepository;
1166
1295
  }
1167
1296
 
1168
1297
  /**
@@ -1210,8 +1339,9 @@ export default class Repository extends EventEmitter {
1210
1339
  * this.entities until all operations have completed. We leave it to subclasses
1211
1340
  * to implement that.
1212
1341
  * @param {object} entity - Optional single entity to save (instead of doing a batch operation on everything)
1342
+ * @param {boolean} useStaged - Save only the staged items, not all
1213
1343
  */
1214
- save = async (entity = null) => {
1344
+ save = async (entity = null, useStaged = false) => {
1215
1345
  if (this.isDestroyed) {
1216
1346
  throw Error('this.save is no longer valid. Repository has been destroyed.');
1217
1347
  }
@@ -1254,6 +1384,9 @@ export default class Repository extends EventEmitter {
1254
1384
  switch(operation) {
1255
1385
  case 'add':
1256
1386
  entities = this.getNonPersisted();
1387
+ if (useStaged) {
1388
+ entities = this.getStaged(entities);
1389
+ }
1257
1390
  if (_.size(entities) > 0) {
1258
1391
  if (this.combineBatch) {
1259
1392
 
@@ -1278,6 +1411,9 @@ export default class Repository extends EventEmitter {
1278
1411
  break;
1279
1412
  case 'edit':
1280
1413
  entities = this.getDirty();
1414
+ if (useStaged) {
1415
+ entities = this.getStaged(entities);
1416
+ }
1281
1417
  if (_.size(entities) > 0) {
1282
1418
  if (this.combineBatch) {
1283
1419
 
@@ -1302,6 +1438,9 @@ export default class Repository extends EventEmitter {
1302
1438
  break;
1303
1439
  case 'delete':
1304
1440
  entities = this.getDeleted();
1441
+ if (useStaged) {
1442
+ entities = this.getStaged(entities);
1443
+ }
1305
1444
  if (_.size(entities) > 0) {
1306
1445
  if (this.combineBatch) {
1307
1446
 
@@ -1459,7 +1598,7 @@ export default class Repository extends EventEmitter {
1459
1598
  * Mainly used for phantom Entities
1460
1599
  * Helper for delete()
1461
1600
  */
1462
- removeEntity = async (entity) => {
1601
+ removeEntity = async (entity) => {
1463
1602
  this.entities = _.filter(this.entities, e => e !== entity);
1464
1603
  entity.destroy();
1465
1604
  }