@onehat/data 1.7.3 → 1.7.7
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
|
});
|
|
@@ -645,8 +650,12 @@ describe('Repository Base', function() {
|
|
|
645
650
|
it('getNonPersisted', async function() {
|
|
646
651
|
this.repository.setAutoSave(false);
|
|
647
652
|
const entity = await this.repository.add({ value: 'six' }),
|
|
648
|
-
|
|
653
|
+
entities = this.repository.entities;
|
|
649
654
|
|
|
655
|
+
let nonPersisted = this.repository.getNonPersisted();
|
|
656
|
+
expect(_.isEqual(entity, nonPersisted[0])).to.be.true;
|
|
657
|
+
|
|
658
|
+
nonPersisted = this.repository.getNonPersisted(entities);
|
|
650
659
|
expect(_.isEqual(entity, nonPersisted[0])).to.be.true;
|
|
651
660
|
});
|
|
652
661
|
|
|
@@ -685,6 +694,15 @@ describe('Repository Base', function() {
|
|
|
685
694
|
expect(_.isEqual(entity, deleted[0])).to.be.true;
|
|
686
695
|
});
|
|
687
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
|
+
|
|
688
706
|
it('isInRepository, hasId', function() {
|
|
689
707
|
this.repository.setAutoSave(false);
|
|
690
708
|
const id = 1,
|
|
@@ -715,6 +733,9 @@ describe('Repository Base', function() {
|
|
|
715
733
|
this.repository.add({ value: 'six' });
|
|
716
734
|
|
|
717
735
|
expect(didFire).to.be.true;
|
|
736
|
+
|
|
737
|
+
// TODO: Need comprehensive test of save(), not just event firing
|
|
738
|
+
|
|
718
739
|
});
|
|
719
740
|
|
|
720
741
|
it('save by entity', async function() {
|
package/package.json
CHANGED
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
|
|
@@ -152,6 +158,7 @@ class Entity extends EventEmitter {
|
|
|
152
158
|
initialize = () => {
|
|
153
159
|
this.properties = this._createProperties();
|
|
154
160
|
this._createMethods();
|
|
161
|
+
this._createStatics();
|
|
155
162
|
this.reset();
|
|
156
163
|
this.isInitialized = true;
|
|
157
164
|
}
|
|
@@ -164,14 +171,30 @@ class Entity extends EventEmitter {
|
|
|
164
171
|
if (this.isDestroyed) {
|
|
165
172
|
throw Error('this._createMethods is no longer valid. Entity has been destroyed.');
|
|
166
173
|
}
|
|
167
|
-
const
|
|
168
|
-
if (!_.isEmpty(
|
|
169
|
-
_.each(
|
|
174
|
+
const methodsDefinitions = this.schema.entity.methods;
|
|
175
|
+
if (!_.isEmpty(methodsDefinitions)) {
|
|
176
|
+
_.each(methodsDefinitions, (method, name) => {
|
|
170
177
|
this[name] = method; // NOTE: Methods must be defined in schema as "function() {}", not as "() => {}" so "this" will be assigned correctly
|
|
171
178
|
});
|
|
172
179
|
}
|
|
173
180
|
}
|
|
174
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Creates the static properties for this Entity, based on Schema.
|
|
184
|
+
* @private
|
|
185
|
+
*/
|
|
186
|
+
_createStatics = () => {
|
|
187
|
+
if (this.isDestroyed) {
|
|
188
|
+
throw Error('this._createStatics is no longer valid. Entity has been destroyed.');
|
|
189
|
+
}
|
|
190
|
+
const staticsDefinitions = this.schema.entity.methods;
|
|
191
|
+
if (!_.isEmpty(staticsDefinitions)) {
|
|
192
|
+
_.each(staticsDefinitions, (value, key) => {
|
|
193
|
+
this[key] = value;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
175
198
|
/**
|
|
176
199
|
* Generates a new unique id and assigns it to this entity.
|
|
177
200
|
*/
|
|
@@ -773,6 +796,14 @@ class Entity extends EventEmitter {
|
|
|
773
796
|
return this.getIdProperty().isTempId;
|
|
774
797
|
}
|
|
775
798
|
|
|
799
|
+
/**
|
|
800
|
+
* Marks this Entity's idProperty's isTempId field
|
|
801
|
+
* @return {boolean} isTempId
|
|
802
|
+
*/
|
|
803
|
+
set isTempId(bool) {
|
|
804
|
+
this.getIdProperty().isTempId = bool;
|
|
805
|
+
}
|
|
806
|
+
|
|
776
807
|
/**
|
|
777
808
|
* Gets the "Display" Property object for this Entity.
|
|
778
809
|
* This is the Property whose value can easily identify the whole Entity itself.
|
|
@@ -1049,6 +1080,7 @@ class Entity extends EventEmitter {
|
|
|
1049
1080
|
this.getIdProperty().isTempId = false;
|
|
1050
1081
|
this._originalData = this._getReconstructedOriginalData();
|
|
1051
1082
|
this._originalDataParsed = this.getParsedValues();
|
|
1083
|
+
this.markStaged(false);
|
|
1052
1084
|
}
|
|
1053
1085
|
|
|
1054
1086
|
/**
|
|
@@ -1094,6 +1126,24 @@ class Entity extends EventEmitter {
|
|
|
1094
1126
|
this.emit('undelete', this._proxy);
|
|
1095
1127
|
}
|
|
1096
1128
|
|
|
1129
|
+
/**
|
|
1130
|
+
* Marks an entity as having been staged for saving.
|
|
1131
|
+
* @param {boolean} bool - How it should be marked. Defaults to true.
|
|
1132
|
+
*/
|
|
1133
|
+
markStaged = (bool = true) => {
|
|
1134
|
+
if (this.isDestroyed) {
|
|
1135
|
+
throw Error('this.markStaged is no longer valid. Entity has been destroyed.');
|
|
1136
|
+
}
|
|
1137
|
+
this.isStaged = bool;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* Convenience function.
|
|
1142
|
+
*/
|
|
1143
|
+
stage = () => {
|
|
1144
|
+
this.markStaged(true);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1097
1147
|
/**
|
|
1098
1148
|
* Destroy this object.
|
|
1099
1149
|
* - Removes all circular references to parent objects
|
|
@@ -1135,52 +1135,163 @@ export default class Repository extends EventEmitter {
|
|
|
1135
1135
|
|
|
1136
1136
|
/**
|
|
1137
1137
|
* Get all phantom (unsaved) Entities
|
|
1138
|
+
* @param {array} entities - Array of entities to filter. Optional. Defaults to this.entities
|
|
1138
1139
|
* @return {Entity[]} Array of phantom Entities, or []
|
|
1139
1140
|
*/
|
|
1140
|
-
getPhantom = () => {
|
|
1141
|
+
getPhantom = (entities = null) => {
|
|
1141
1142
|
if (this.isDestroyed) {
|
|
1142
1143
|
throw Error('this.getPhantom is no longer valid. Repository has been destroyed.');
|
|
1143
1144
|
}
|
|
1144
|
-
|
|
1145
|
+
if (!entities) {
|
|
1146
|
+
entities = this.entities;
|
|
1147
|
+
}
|
|
1148
|
+
return _.filter(this.entities, entity => entity.isPhantom);
|
|
1145
1149
|
}
|
|
1146
1150
|
|
|
1147
1151
|
/**
|
|
1148
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
|
|
1149
1154
|
* @return {Entity[]} Array of dirty Entities, or []
|
|
1150
1155
|
*/
|
|
1151
|
-
getNonPersisted = () => {
|
|
1156
|
+
getNonPersisted = (entities = null) => {
|
|
1152
1157
|
if (this.isDestroyed) {
|
|
1153
1158
|
throw Error('this.getDirty is no longer valid. Repository has been destroyed.');
|
|
1154
1159
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1160
|
+
if (!entities) {
|
|
1161
|
+
entities = this.entities;
|
|
1162
|
+
}
|
|
1163
|
+
return _.filter(this.entities, entity => !entity.isPersisted);
|
|
1158
1164
|
}
|
|
1159
1165
|
|
|
1160
1166
|
/**
|
|
1161
1167
|
* Get all dirty (having unsaved changes) Entities
|
|
1168
|
+
* @param {array} entities - Array of entities to filter. Optional. Defaults to this.entities
|
|
1162
1169
|
* @return {Entity[]} Array of dirty Entities, or []
|
|
1163
1170
|
*/
|
|
1164
|
-
getDirty = () => {
|
|
1171
|
+
getDirty = (entities = null) => {
|
|
1165
1172
|
if (this.isDestroyed) {
|
|
1166
1173
|
throw Error('this.getDirty is no longer valid. Repository has been destroyed.');
|
|
1167
1174
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
}
|
|
1175
|
+
if (!entities) {
|
|
1176
|
+
entities = this.entities;
|
|
1177
|
+
}
|
|
1178
|
+
return _.filter(entities, entity => entity.isDirty);
|
|
1171
1179
|
}
|
|
1172
1180
|
|
|
1173
1181
|
/**
|
|
1174
1182
|
* Get all deleted Entities
|
|
1183
|
+
* @param {array} entities - Array of entities to filter. Optional. Defaults to this.entities
|
|
1175
1184
|
* @return {Entity[]} Array of deleted Entities, or []
|
|
1176
1185
|
*/
|
|
1177
|
-
getDeleted = () => {
|
|
1186
|
+
getDeleted = (entities = null) => {
|
|
1178
1187
|
if (this.isDestroyed) {
|
|
1179
1188
|
throw Error('this.getDeleted is no longer valid. Repository has been destroyed.');
|
|
1180
1189
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
}
|
|
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;
|
|
1184
1295
|
}
|
|
1185
1296
|
|
|
1186
1297
|
/**
|
|
@@ -1220,6 +1331,13 @@ export default class Repository extends EventEmitter {
|
|
|
1220
1331
|
return this.isInRepository(id);
|
|
1221
1332
|
}
|
|
1222
1333
|
|
|
1334
|
+
/**
|
|
1335
|
+
* Convenience function
|
|
1336
|
+
*/
|
|
1337
|
+
saveStaged = () => {
|
|
1338
|
+
return this.save(null, true);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1223
1341
|
/**
|
|
1224
1342
|
* Queues up batch operations for saving
|
|
1225
1343
|
* new, edited, and deleted entities to storage medium.
|
|
@@ -1228,8 +1346,9 @@ export default class Repository extends EventEmitter {
|
|
|
1228
1346
|
* this.entities until all operations have completed. We leave it to subclasses
|
|
1229
1347
|
* to implement that.
|
|
1230
1348
|
* @param {object} entity - Optional single entity to save (instead of doing a batch operation on everything)
|
|
1349
|
+
* @param {boolean} useStaged - Save only the staged items, not all
|
|
1231
1350
|
*/
|
|
1232
|
-
save = async (entity = null) => {
|
|
1351
|
+
save = async (entity = null, useStaged = false) => {
|
|
1233
1352
|
if (this.isDestroyed) {
|
|
1234
1353
|
throw Error('this.save is no longer valid. Repository has been destroyed.');
|
|
1235
1354
|
}
|
|
@@ -1272,6 +1391,9 @@ export default class Repository extends EventEmitter {
|
|
|
1272
1391
|
switch(operation) {
|
|
1273
1392
|
case 'add':
|
|
1274
1393
|
entities = this.getNonPersisted();
|
|
1394
|
+
if (useStaged) {
|
|
1395
|
+
entities = this.getStaged(entities);
|
|
1396
|
+
}
|
|
1275
1397
|
if (_.size(entities) > 0) {
|
|
1276
1398
|
if (this.combineBatch) {
|
|
1277
1399
|
|
|
@@ -1296,6 +1418,9 @@ export default class Repository extends EventEmitter {
|
|
|
1296
1418
|
break;
|
|
1297
1419
|
case 'edit':
|
|
1298
1420
|
entities = this.getDirty();
|
|
1421
|
+
if (useStaged) {
|
|
1422
|
+
entities = this.getStaged(entities);
|
|
1423
|
+
}
|
|
1299
1424
|
if (_.size(entities) > 0) {
|
|
1300
1425
|
if (this.combineBatch) {
|
|
1301
1426
|
|
|
@@ -1320,6 +1445,9 @@ export default class Repository extends EventEmitter {
|
|
|
1320
1445
|
break;
|
|
1321
1446
|
case 'delete':
|
|
1322
1447
|
entities = this.getDeleted();
|
|
1448
|
+
if (useStaged) {
|
|
1449
|
+
entities = this.getStaged(entities);
|
|
1450
|
+
}
|
|
1323
1451
|
if (_.size(entities) > 0) {
|
|
1324
1452
|
if (this.combineBatch) {
|
|
1325
1453
|
|
|
@@ -1477,7 +1605,7 @@ export default class Repository extends EventEmitter {
|
|
|
1477
1605
|
* Mainly used for phantom Entities
|
|
1478
1606
|
* Helper for delete()
|
|
1479
1607
|
*/
|
|
1480
|
-
|
|
1608
|
+
removeEntity = async (entity) => {
|
|
1481
1609
|
this.entities = _.filter(this.entities, e => e !== entity);
|
|
1482
1610
|
entity.destroy();
|
|
1483
1611
|
}
|
package/src/Schema/Schema.js
CHANGED