@onehat/data 1.9.2 → 1.10.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 +42 -2
- package/cypress/integration/OneHatData.spec.js +26 -0
- package/cypress/integration/Property/Property.spec.js +48 -0
- package/package.json +2 -1
- package/src/Entity.js +51 -1
- package/src/OneHatData.js +19 -0
- package/src/Property/Property.js +52 -2
- package/src/Schema/KeyValues.js +7 -0
- package/src/Schema/Schema.js +5 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Joi from 'Joi';
|
|
1
2
|
import Entity from '../../src/Entity.js';
|
|
2
3
|
import Schema from '../../src/Schema/index.js';
|
|
3
4
|
import PropertyTypes from '../../src/Property/index.js';
|
|
@@ -16,13 +17,17 @@ describe('Entity', function() {
|
|
|
16
17
|
{ name: 'bar' },
|
|
17
18
|
{ name: 'baz', mapping: 'baz.test.val', type: 'bool', defaultValue: null, },
|
|
18
19
|
],
|
|
20
|
+
validator: null,
|
|
19
21
|
},
|
|
20
22
|
entity: {
|
|
21
23
|
methods: {
|
|
22
24
|
testMethod: function() {
|
|
23
|
-
this.
|
|
25
|
+
this.azx = 'test me';
|
|
24
26
|
},
|
|
25
27
|
},
|
|
28
|
+
statics: {
|
|
29
|
+
azx: null,
|
|
30
|
+
},
|
|
26
31
|
},
|
|
27
32
|
});
|
|
28
33
|
this.data = {
|
|
@@ -116,7 +121,7 @@ describe('Entity', function() {
|
|
|
116
121
|
|
|
117
122
|
it('_createMethods', function() {
|
|
118
123
|
this.entity.testMethod();
|
|
119
|
-
expect(this.entity.
|
|
124
|
+
expect(this.entity.azx).to.be.eq('test me');
|
|
120
125
|
});
|
|
121
126
|
|
|
122
127
|
it('loadOriginalData', function() {
|
|
@@ -737,7 +742,42 @@ describe('Entity', function() {
|
|
|
737
742
|
property.setValue('Test');
|
|
738
743
|
expect(didFire).to.be.true;
|
|
739
744
|
});
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
describe('validators', function() {
|
|
748
|
+
|
|
749
|
+
it('whole validation process', function() {
|
|
750
|
+
let didFire = false;
|
|
751
|
+
this.entity.on('changeValidity', (entity) => {
|
|
752
|
+
didFire = true;
|
|
753
|
+
});
|
|
740
754
|
|
|
755
|
+
// Initial condition
|
|
756
|
+
expect(this.entity.isValid).to.be.null;
|
|
757
|
+
|
|
758
|
+
// Add validators
|
|
759
|
+
this.schema.model.validator = Joi.object({
|
|
760
|
+
foo: Joi.number()
|
|
761
|
+
.integer(),
|
|
762
|
+
bar: Joi.string()
|
|
763
|
+
.alphanum()
|
|
764
|
+
.required(),
|
|
765
|
+
baz: Joi.any(),
|
|
766
|
+
});
|
|
767
|
+
const result = this.entity.validate();
|
|
768
|
+
expect(result).to.be.true;
|
|
769
|
+
|
|
770
|
+
// Set a property to be invalid
|
|
771
|
+
this.entity.bar = null;
|
|
772
|
+
expect(didFire).to.be.true;
|
|
773
|
+
expect(this.entity.isValid).to.be.false;
|
|
774
|
+
expect(this.entity.validationError).to.match(/"bar" must be a string/);
|
|
775
|
+
|
|
776
|
+
// Restore validity
|
|
777
|
+
this.entity.bar = 'test';
|
|
778
|
+
expect(this.entity.isValid).to.be.true;
|
|
779
|
+
expect(this.entity.validationError).to.be.null;
|
|
780
|
+
});
|
|
741
781
|
});
|
|
742
782
|
|
|
743
783
|
});
|
|
@@ -12,6 +12,15 @@ async function beforeEach() {
|
|
|
12
12
|
this.oneHatData = new OneHatData();
|
|
13
13
|
this.schema = this.oneHatData.createSchema({
|
|
14
14
|
name: 'bar',
|
|
15
|
+
model: {
|
|
16
|
+
idProperty: 'key',
|
|
17
|
+
displayProperty: 'value',
|
|
18
|
+
properties: [
|
|
19
|
+
{ name: 'key', },
|
|
20
|
+
{ name: 'value', },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
repository: 'memory',
|
|
15
24
|
});
|
|
16
25
|
await this.oneHatData.createRepository({
|
|
17
26
|
id: 'foo',
|
|
@@ -334,6 +343,23 @@ describe('OneHatData', function() {
|
|
|
334
343
|
})();
|
|
335
344
|
});
|
|
336
345
|
|
|
346
|
+
it('isEntity', async function() {
|
|
347
|
+
(async function() {
|
|
348
|
+
debugger;
|
|
349
|
+
await beforeEach();
|
|
350
|
+
const oneHatData = this.oneHatData;
|
|
351
|
+
const repository = oneHatData.getRepository('bar');
|
|
352
|
+
const entity = await repository.add({ key: 1, value: 'value', });
|
|
353
|
+
|
|
354
|
+
expect(isEntity(entity)).to.be.true;
|
|
355
|
+
expect(isEntity({})).to.be.false;
|
|
356
|
+
expect(isEntity(2)).to.be.false;
|
|
357
|
+
expect(isEntity([1,2])).to.be.false;
|
|
358
|
+
|
|
359
|
+
afterEach();
|
|
360
|
+
})();
|
|
361
|
+
});
|
|
362
|
+
|
|
337
363
|
it('destroy', function() {
|
|
338
364
|
(async function() {
|
|
339
365
|
await beforeEach();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Joi from 'Joi';
|
|
1
2
|
import PropertyTypes from '../../../src/Property/index.js';
|
|
2
3
|
import Entity from '../../../src/Entity.js';
|
|
3
4
|
import Schema from '../../../src/Schema/index.js';
|
|
@@ -233,4 +234,51 @@ describe('Property', function() {
|
|
|
233
234
|
|
|
234
235
|
});
|
|
235
236
|
|
|
237
|
+
describe('validators', function() {
|
|
238
|
+
|
|
239
|
+
it('whole validation process', function() {
|
|
240
|
+
let didFire = false;
|
|
241
|
+
this.property.on('changeValidity', (entity) => {
|
|
242
|
+
didFire = true;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Initial condition
|
|
246
|
+
expect(this.property.isValid).to.be.null;
|
|
247
|
+
|
|
248
|
+
// Add validators
|
|
249
|
+
const schema = new Schema({
|
|
250
|
+
name: 'baz',
|
|
251
|
+
model: {
|
|
252
|
+
idProperty: 'foo',
|
|
253
|
+
displayProperty: 'bar',
|
|
254
|
+
properties: [
|
|
255
|
+
{ name: 'foo', type: 'int' },
|
|
256
|
+
{ name: 'bar' },
|
|
257
|
+
],
|
|
258
|
+
validator: Joi.object({
|
|
259
|
+
foo: Joi.number()
|
|
260
|
+
.integer(),
|
|
261
|
+
}),
|
|
262
|
+
},
|
|
263
|
+
}),
|
|
264
|
+
entity = new Entity(schema);
|
|
265
|
+
entity.initialize();
|
|
266
|
+
const property = entity.getProperty('foo');
|
|
267
|
+
property.setValue(2);
|
|
268
|
+
|
|
269
|
+
const result = property.validate();
|
|
270
|
+
expect(result).to.be.true;
|
|
271
|
+
|
|
272
|
+
// Set a property to be invalid
|
|
273
|
+
property.setValue(null);
|
|
274
|
+
expect(property.isValid).to.be.false;
|
|
275
|
+
expect(property.validationError).to.match(/"foo" must be a number/);
|
|
276
|
+
|
|
277
|
+
// Restore validity
|
|
278
|
+
property.setValue(2);
|
|
279
|
+
expect(property.isValid).to.be.true;
|
|
280
|
+
expect(property.validationError).to.be.null;
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
236
284
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onehat/data",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "JS data modeling package with adapters for many storage mediums.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"chrono-node": "^2.4.2",
|
|
42
42
|
"fast-xml-parser": "^4.0.12",
|
|
43
43
|
"he": "^1.2.0",
|
|
44
|
+
"joi": "^17.7.0",
|
|
44
45
|
"js-base64": "^3.7.3",
|
|
45
46
|
"lodash": "^4.17.21",
|
|
46
47
|
"moment": "^2.29.4",
|
package/src/Entity.js
CHANGED
|
@@ -49,6 +49,7 @@ class Entity extends EventEmitter {
|
|
|
49
49
|
|
|
50
50
|
this.registerEvents([
|
|
51
51
|
'change',
|
|
52
|
+
'changeValidity',
|
|
52
53
|
'reset',
|
|
53
54
|
'reload',
|
|
54
55
|
'save',
|
|
@@ -139,6 +140,17 @@ class Entity extends EventEmitter {
|
|
|
139
140
|
* @member {string} isFrozen - Prevent the entity from being destroyed, but don't let it be changed either.
|
|
140
141
|
*/
|
|
141
142
|
this.isFrozen = false;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @member {boolean} isValid - Whether this Entity passes validation
|
|
146
|
+
* @private
|
|
147
|
+
*/
|
|
148
|
+
this.isValid = null;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @member {object} validationError - Any error in last validation.
|
|
152
|
+
*/
|
|
153
|
+
this.validationError = null;
|
|
142
154
|
|
|
143
155
|
|
|
144
156
|
// This ES6 Proxy allows us to create magic getters and setters for all property values.
|
|
@@ -268,7 +280,6 @@ class Entity extends EventEmitter {
|
|
|
268
280
|
}
|
|
269
281
|
const Property = PropertyTypes[type],
|
|
270
282
|
property = new Property(definition, this._proxy);
|
|
271
|
-
|
|
272
283
|
property.on('change', this._onPropertyChange);
|
|
273
284
|
|
|
274
285
|
properties[definition.name] = property;
|
|
@@ -286,6 +297,7 @@ class Entity extends EventEmitter {
|
|
|
286
297
|
*/
|
|
287
298
|
_onPropertyChange = () => {
|
|
288
299
|
this._recalculateDependentProperties();
|
|
300
|
+
this.validate();
|
|
289
301
|
this.emit('change', this._proxy);
|
|
290
302
|
}
|
|
291
303
|
|
|
@@ -1136,6 +1148,7 @@ class Entity extends EventEmitter {
|
|
|
1136
1148
|
|
|
1137
1149
|
if (isChanged) {
|
|
1138
1150
|
this._recalculateDependentProperties();
|
|
1151
|
+
this.validate();
|
|
1139
1152
|
this.emit('change', this._proxy);
|
|
1140
1153
|
}
|
|
1141
1154
|
return isChanged;
|
|
@@ -1286,6 +1299,43 @@ class Entity extends EventEmitter {
|
|
|
1286
1299
|
this.isFrozen = true;
|
|
1287
1300
|
}
|
|
1288
1301
|
|
|
1302
|
+
|
|
1303
|
+
// _ __ ___ __ __ _
|
|
1304
|
+
// | | / /___ _/ (_)___/ /___ _/ /_(_)___ ____
|
|
1305
|
+
// | | / / __ `/ / / __ / __ `/ __/ / __ \/ __ \
|
|
1306
|
+
// | |/ / /_/ / / / /_/ / /_/ / /_/ / /_/ / / / /
|
|
1307
|
+
// |___/\__,_/_/_/\__,_/\__,_/\__/_/\____/_/ /_/
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* Gets whether or not the Entity validates according to schema's validation rules
|
|
1311
|
+
* @return {boolean} isValid
|
|
1312
|
+
*/
|
|
1313
|
+
validate = () => {
|
|
1314
|
+
if (this.isDestroyed) {
|
|
1315
|
+
throw Error('this.validate is no longer valid. Entity has been destroyed.');
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
let isValid = null,
|
|
1319
|
+
error;
|
|
1320
|
+
|
|
1321
|
+
if (this.schema.model.validator) {
|
|
1322
|
+
const validationResult = this.schema.model.validator.validate(this.submitValues);
|
|
1323
|
+
error = validationResult && validationResult.error || null;
|
|
1324
|
+
isValid = !error;
|
|
1325
|
+
if (this.validationError !== error) {
|
|
1326
|
+
this.validationError = error;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
if (this.isValid !== isValid) {
|
|
1331
|
+
this.emit('changeValidity', this._proxy, isValid);
|
|
1332
|
+
this.isValid = isValid;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
return isValid;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
|
|
1289
1339
|
/**
|
|
1290
1340
|
* Destroy this object.
|
|
1291
1341
|
* - Removes all circular references to parent objects
|
package/src/OneHatData.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import EventEmitter from '@onehat/events';
|
|
4
4
|
import CoreRepositoryTypes from './Repository/index.js';
|
|
5
|
+
import Entity from './Entity.js';
|
|
5
6
|
import {
|
|
6
7
|
MODE_LOCAL_MIRROR,
|
|
7
8
|
MODE_COMMAND_QUEUE,
|
|
@@ -620,6 +621,24 @@ export class OneHatData extends EventEmitter {
|
|
|
620
621
|
return this;
|
|
621
622
|
}
|
|
622
623
|
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
// _ __ ___ __ __
|
|
628
|
+
// | | / /___ _/ (_)___/ /___ _/ /____
|
|
629
|
+
// | | / / __ `/ / / __ / __ `/ __/ _ \
|
|
630
|
+
// | |/ / /_/ / / / /_/ / /_/ / /_/ __/
|
|
631
|
+
// |___/\__,_/_/_/\__,_/\__,_/\__/\___/
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Determines if submitted object is an entity
|
|
635
|
+
* @return boolean
|
|
636
|
+
*/
|
|
637
|
+
isEntity = (obj) => {
|
|
638
|
+
return obj?.__proto__?.constructor === Entity;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
|
|
623
642
|
/**
|
|
624
643
|
* Destroy this object.
|
|
625
644
|
* - Removes child objects
|
package/src/Property/Property.js
CHANGED
|
@@ -135,6 +135,7 @@ export default class Property extends EventEmitter {
|
|
|
135
135
|
|
|
136
136
|
this.registerEvents([
|
|
137
137
|
'change',
|
|
138
|
+
'changeValidity',
|
|
138
139
|
'destroy',
|
|
139
140
|
]);
|
|
140
141
|
|
|
@@ -148,17 +149,28 @@ export default class Property extends EventEmitter {
|
|
|
148
149
|
* @member {any} rawValue - The raw value supplied to this property, *before* any parsing was applied
|
|
149
150
|
*/
|
|
150
151
|
this.rawValue = null;
|
|
151
|
-
|
|
152
|
+
|
|
152
153
|
/**
|
|
153
154
|
* @member {any} parsedValue - The value for this property, *after* any parsing was applied
|
|
154
155
|
*/
|
|
155
156
|
this.parsedValue = null;
|
|
156
|
-
|
|
157
|
+
|
|
157
158
|
/**
|
|
158
159
|
* @member {boolean} isDestroyed - Whether this object has been destroyed
|
|
159
160
|
* @private
|
|
160
161
|
*/
|
|
161
162
|
this.isDestroyed = false;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @member {boolean} isValid - Whether this Property passes validation
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
this.isValid = null;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @member {object} validationError - Any error in last validation.
|
|
172
|
+
*/
|
|
173
|
+
this.validationError = null;
|
|
162
174
|
|
|
163
175
|
}
|
|
164
176
|
|
|
@@ -357,6 +369,7 @@ export default class Property extends EventEmitter {
|
|
|
357
369
|
if (isChanged) {
|
|
358
370
|
this.rawValue = rawValue;
|
|
359
371
|
this.parsedValue = newValue;
|
|
372
|
+
this.validate();
|
|
360
373
|
this.emit('change', this, oldValue, newValue);
|
|
361
374
|
}
|
|
362
375
|
|
|
@@ -433,6 +446,43 @@ export default class Property extends EventEmitter {
|
|
|
433
446
|
return this.mapping;
|
|
434
447
|
}
|
|
435
448
|
|
|
449
|
+
// _ __ ___ __ __ _
|
|
450
|
+
// | | / /___ _/ (_)___/ /___ _/ /_(_)___ ____
|
|
451
|
+
// | | / / __ `/ / / __ / __ `/ __/ / __ \/ __ \
|
|
452
|
+
// | |/ / /_/ / / / /_/ / /_/ / /_/ / /_/ / / / /
|
|
453
|
+
// |___/\__,_/_/_/\__,_/\__,_/\__/_/\____/_/ /_/
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Gets whether or not the Property validates according to schema's validation rules
|
|
457
|
+
* @return {boolean} isValid
|
|
458
|
+
*/
|
|
459
|
+
validate = () => {
|
|
460
|
+
if (this.isDestroyed) {
|
|
461
|
+
throw Error('this.validate is no longer valid. Entity has been destroyed.');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let isValid = null,
|
|
465
|
+
error;
|
|
466
|
+
|
|
467
|
+
if (this._entity?.schema?.model?.validator) {
|
|
468
|
+
const obj = {};
|
|
469
|
+
obj[this.name] = this.submitValue;
|
|
470
|
+
const validationResult = this._entity.schema.model.validator.validate(obj);
|
|
471
|
+
error = validationResult && validationResult.error || null;
|
|
472
|
+
isValid = !error;
|
|
473
|
+
if (this.validationError !== error) {
|
|
474
|
+
this.validationError = error;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (this.isValid !== isValid) {
|
|
479
|
+
this.emit('changeValidity', this._proxy, isValid);
|
|
480
|
+
this.isValid = isValid;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return isValid;
|
|
484
|
+
}
|
|
485
|
+
|
|
436
486
|
/**
|
|
437
487
|
* Destroy this object.
|
|
438
488
|
* - Removes all circular references to parent objects
|
package/src/Schema/KeyValues.js
CHANGED
package/src/Schema/Schema.js
CHANGED
|
@@ -72,6 +72,11 @@ export default class Schema extends EventEmitter {
|
|
|
72
72
|
*/
|
|
73
73
|
sorters: [],
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* @member {object} validators - A JOI schema. See JOI docs for more info (https://joi.dev)
|
|
77
|
+
*/
|
|
78
|
+
validator: null,
|
|
79
|
+
|
|
75
80
|
/**
|
|
76
81
|
* @member {object} associations - List of associated Models
|
|
77
82
|
*/
|