electrodb 1.4.7 → 1.6.1
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/CHANGELOG.md +31 -4
- package/README.md +151 -61
- package/index.d.ts +41 -1
- package/package.json +6 -3
- package/src/entity.js +177 -47
- package/src/errors.js +104 -8
- package/src/schema.js +101 -50
- package/tsconfig.json +1 -1
package/src/schema.js
CHANGED
|
@@ -289,7 +289,7 @@ class Attribute {
|
|
|
289
289
|
|
|
290
290
|
_makeCast(name, cast) {
|
|
291
291
|
if (cast !== undefined && !CastTypes.includes(cast)) {
|
|
292
|
-
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "cast" property for attribute: "${name}". Acceptable types include ${CastTypes.join(", "
|
|
292
|
+
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "cast" property for attribute: "${name}". Acceptable types include ${CastTypes.join(", ")}`,
|
|
293
293
|
);
|
|
294
294
|
} else if (cast === AttributeTypes.string) {
|
|
295
295
|
return (val) => {
|
|
@@ -327,20 +327,34 @@ class Attribute {
|
|
|
327
327
|
_makeValidate(definition) {
|
|
328
328
|
if (typeof definition === "function") {
|
|
329
329
|
return (val) => {
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
try {
|
|
331
|
+
let reason = definition(val);
|
|
332
|
+
const isValid = !reason;
|
|
333
|
+
if (isValid) {
|
|
334
|
+
return [isValid, []];
|
|
335
|
+
} else if (typeof reason === "boolean") {
|
|
336
|
+
return [isValid, [new e.ElectroUserValidationError(this.path, "Invalid value provided")]];
|
|
337
|
+
} else {
|
|
338
|
+
return [isValid, [new e.ElectroUserValidationError(this.path, reason)]];
|
|
339
|
+
}
|
|
340
|
+
} catch(err) {
|
|
341
|
+
return [false, [new e.ElectroUserValidationError(this.path, err)]];
|
|
342
|
+
}
|
|
332
343
|
};
|
|
333
344
|
} else if (definition instanceof RegExp) {
|
|
334
345
|
return (val) => {
|
|
335
346
|
if (val === undefined) {
|
|
336
|
-
return [true,
|
|
347
|
+
return [true, []];
|
|
337
348
|
}
|
|
338
349
|
let isValid = definition.test(val);
|
|
339
|
-
let reason =
|
|
350
|
+
let reason = [];
|
|
351
|
+
if (!isValid) {
|
|
352
|
+
reason.push(new e.ElectroUserValidationError(this.path, `Invalid value for attribute "${this.path}": Failed model defined regex`));
|
|
353
|
+
}
|
|
340
354
|
return [isValid, reason];
|
|
341
355
|
};
|
|
342
356
|
} else {
|
|
343
|
-
return (
|
|
357
|
+
return () => [true, []];
|
|
344
358
|
}
|
|
345
359
|
}
|
|
346
360
|
|
|
@@ -385,15 +399,19 @@ class Attribute {
|
|
|
385
399
|
|
|
386
400
|
_isType(value) {
|
|
387
401
|
if (value === undefined) {
|
|
388
|
-
|
|
402
|
+
let reason = [];
|
|
403
|
+
if (this.required) {
|
|
404
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value is required.`));
|
|
405
|
+
}
|
|
406
|
+
return [!this.required, reason];
|
|
389
407
|
}
|
|
390
408
|
let isTyped = false;
|
|
391
|
-
let reason =
|
|
409
|
+
let reason = [];
|
|
392
410
|
switch (this.type) {
|
|
393
411
|
case AttributeTypes.enum:
|
|
394
412
|
isTyped = this.enumArray.includes(value);
|
|
395
413
|
if (!isTyped) {
|
|
396
|
-
reason
|
|
414
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value not found in set of acceptable values: ${u.commaSeparatedString(this.enumArray)}`));
|
|
397
415
|
}
|
|
398
416
|
break;
|
|
399
417
|
case AttributeTypes.any:
|
|
@@ -405,7 +423,7 @@ class Attribute {
|
|
|
405
423
|
default:
|
|
406
424
|
isTyped = typeof value === this.type;
|
|
407
425
|
if (!isTyped) {
|
|
408
|
-
reason
|
|
426
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Received value of type "${typeof value}", expected value of type "${this.type}"`));
|
|
409
427
|
}
|
|
410
428
|
break;
|
|
411
429
|
}
|
|
@@ -414,12 +432,12 @@ class Attribute {
|
|
|
414
432
|
|
|
415
433
|
isValid(value) {
|
|
416
434
|
try {
|
|
417
|
-
let [isTyped,
|
|
418
|
-
let [isValid, validationError] = this.validate(value);
|
|
419
|
-
let
|
|
420
|
-
return [isTyped && isValid,
|
|
435
|
+
let [isTyped, typeErrorReason] = this._isType(value);
|
|
436
|
+
let [isValid, validationError] = isTyped ? this.validate(value) : [false, []];
|
|
437
|
+
let errors = [...typeErrorReason, ...validationError].filter(value => value !== undefined);
|
|
438
|
+
return [isTyped && isValid, errors];
|
|
421
439
|
} catch (err) {
|
|
422
|
-
return [false, err
|
|
440
|
+
return [false, [err]];
|
|
423
441
|
}
|
|
424
442
|
}
|
|
425
443
|
|
|
@@ -433,10 +451,9 @@ class Attribute {
|
|
|
433
451
|
|
|
434
452
|
getValidate(value) {
|
|
435
453
|
value = this.val(value);
|
|
436
|
-
let [isValid,
|
|
454
|
+
let [isValid, validationErrors] = this.isValid(value);
|
|
437
455
|
if (!isValid) {
|
|
438
|
-
|
|
439
|
-
throw new Error(validationError);
|
|
456
|
+
throw new e.ElectroValidationError(validationErrors);
|
|
440
457
|
}
|
|
441
458
|
return value;
|
|
442
459
|
}
|
|
@@ -509,13 +526,17 @@ class MapAttribute extends Attribute {
|
|
|
509
526
|
|
|
510
527
|
_isType(value) {
|
|
511
528
|
if (value === undefined) {
|
|
512
|
-
|
|
529
|
+
let reason = [];
|
|
530
|
+
if (this.required) {
|
|
531
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value is required.`));
|
|
532
|
+
}
|
|
533
|
+
return [!this.required, reason];
|
|
513
534
|
}
|
|
514
535
|
const valueType = getValueType(value);
|
|
515
536
|
if (valueType !== ValueTypes.object) {
|
|
516
|
-
return [false, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "object"`];
|
|
537
|
+
return [false, [new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "object"`)]];
|
|
517
538
|
}
|
|
518
|
-
let reason =
|
|
539
|
+
let reason = [];
|
|
519
540
|
const [childrenAreValid, childErrors] = this._validateChildren(value);
|
|
520
541
|
if (!childrenAreValid) {
|
|
521
542
|
reason = childErrors;
|
|
@@ -526,24 +547,24 @@ class MapAttribute extends Attribute {
|
|
|
526
547
|
_validateChildren(value) {
|
|
527
548
|
const valueType = getValueType(value);
|
|
528
549
|
const attributes = this.properties.attributes;
|
|
529
|
-
|
|
550
|
+
let errors = [];
|
|
530
551
|
if (valueType === ValueTypes.object) {
|
|
531
552
|
for (const child of Object.keys(attributes)) {
|
|
532
|
-
const [isValid,
|
|
553
|
+
const [isValid, errorValues] = attributes[child].isValid(value === undefined ? value : value[child])
|
|
533
554
|
if (!isValid) {
|
|
534
|
-
errors
|
|
555
|
+
errors = [...errors, ...errorValues]
|
|
535
556
|
}
|
|
536
557
|
}
|
|
537
558
|
} else if (valueType !== ValueTypes.object) {
|
|
538
559
|
errors.push(
|
|
539
|
-
`Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"`
|
|
560
|
+
new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"`)
|
|
540
561
|
);
|
|
541
562
|
} else if (this.properties.hasRequiredAttributes) {
|
|
542
563
|
errors.push(
|
|
543
|
-
`Invalid value type at entity path: "${this.path}". Map attribute requires at least the properties ${u.commaSeparatedString(Object.keys(attributes))}`
|
|
564
|
+
new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Map attribute requires at least the properties ${u.commaSeparatedString(Object.keys(attributes))}`)
|
|
544
565
|
);
|
|
545
566
|
}
|
|
546
|
-
return [errors.length === 0, errors
|
|
567
|
+
return [errors.length === 0, errors];
|
|
547
568
|
}
|
|
548
569
|
|
|
549
570
|
val(value) {
|
|
@@ -560,7 +581,7 @@ class MapAttribute extends Attribute {
|
|
|
560
581
|
} else if (value && valueType !== "object" && Object.keys(value).length === 0) {
|
|
561
582
|
return getValue(value);
|
|
562
583
|
} else if (valueType !== "object") {
|
|
563
|
-
throw new
|
|
584
|
+
throw new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"`);
|
|
564
585
|
}
|
|
565
586
|
|
|
566
587
|
const data = {};
|
|
@@ -643,24 +664,29 @@ class ListAttribute extends Attribute {
|
|
|
643
664
|
}
|
|
644
665
|
|
|
645
666
|
_validateArrayValue(value) {
|
|
667
|
+
const reason = [];
|
|
646
668
|
const valueType = getValueType(value);
|
|
647
669
|
if (value !== undefined && valueType !== ValueTypes.array) {
|
|
648
|
-
return [false, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "array"`];
|
|
670
|
+
return [false, [new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "array"`)]];
|
|
649
671
|
} else {
|
|
650
|
-
return [true,
|
|
672
|
+
return [true, []];
|
|
651
673
|
}
|
|
652
674
|
}
|
|
653
675
|
|
|
654
676
|
_isType(value) {
|
|
655
677
|
if (value === undefined) {
|
|
656
|
-
|
|
678
|
+
let reason = [];
|
|
679
|
+
if (this.required) {
|
|
680
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value is required.`));
|
|
681
|
+
}
|
|
682
|
+
return [!this.required, reason];
|
|
657
683
|
}
|
|
658
684
|
|
|
659
685
|
const [isValidArray, errors] = this._validateArrayValue(value);
|
|
660
686
|
if (!isValidArray) {
|
|
661
687
|
return [isValidArray, errors];
|
|
662
688
|
}
|
|
663
|
-
let reason =
|
|
689
|
+
let reason = [];
|
|
664
690
|
const [childrenAreValid, childErrors] = this._validateChildren(value);
|
|
665
691
|
if (!childrenAreValid) {
|
|
666
692
|
reason = childErrors;
|
|
@@ -673,17 +699,22 @@ class ListAttribute extends Attribute {
|
|
|
673
699
|
const errors = [];
|
|
674
700
|
if (valueType === ValueTypes.array) {
|
|
675
701
|
for (const i in value) {
|
|
676
|
-
const [isValid,
|
|
702
|
+
const [isValid, errorValues] = this.items.isValid(value[i]);
|
|
677
703
|
if (!isValid) {
|
|
678
|
-
|
|
704
|
+
for (const err of errorValues) {
|
|
705
|
+
if (err instanceof e.ElectroAttributeValidationError || err instanceof e.ElectroUserValidationError) {
|
|
706
|
+
err.index = parseInt(i);
|
|
707
|
+
}
|
|
708
|
+
errors.push(err);
|
|
709
|
+
}
|
|
679
710
|
}
|
|
680
711
|
}
|
|
681
712
|
} else {
|
|
682
713
|
errors.push(
|
|
683
|
-
`Invalid value type at entity path: "${this.path}". Expected value to be an Array to fulfill attribute type "${this.type}"`
|
|
714
|
+
new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Expected value to be an Array to fulfill attribute type "${this.type}"`)
|
|
684
715
|
);
|
|
685
716
|
}
|
|
686
|
-
return [errors.length === 0, errors
|
|
717
|
+
return [errors.length === 0, errors];
|
|
687
718
|
}
|
|
688
719
|
|
|
689
720
|
val(value) {
|
|
@@ -700,7 +731,7 @@ class ListAttribute extends Attribute {
|
|
|
700
731
|
} else if (Array.isArray(value) && value.length === 0) {
|
|
701
732
|
return value;
|
|
702
733
|
} else if (!Array.isArray(value)) {
|
|
703
|
-
throw new
|
|
734
|
+
throw new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path "${this.path}. Received value of type "${getValueType(value)}", expected value of type "array"`);
|
|
704
735
|
}
|
|
705
736
|
|
|
706
737
|
const data = [];
|
|
@@ -731,6 +762,20 @@ class SetAttribute extends Attribute {
|
|
|
731
762
|
this.items = items;
|
|
732
763
|
this.get = this._makeGet(definition.get, items);
|
|
733
764
|
this.set = this._makeSet(definition.set, items);
|
|
765
|
+
this.validate = this._makeSetValidate(definition);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
_makeSetValidate(definition) {
|
|
769
|
+
const validate = this._makeValidate(definition.validate);
|
|
770
|
+
return (value) => {
|
|
771
|
+
if (Array.isArray(value)) {
|
|
772
|
+
return validate([...value]);
|
|
773
|
+
} else if (value && value.wrapperName === 'Set') {
|
|
774
|
+
return validate([...value.values])
|
|
775
|
+
} else {
|
|
776
|
+
return validate(value);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
734
779
|
}
|
|
735
780
|
|
|
736
781
|
fromDDBSet(value) {
|
|
@@ -768,7 +813,7 @@ class SetAttribute extends Attribute {
|
|
|
768
813
|
return this._createDDBSet(value);
|
|
769
814
|
}
|
|
770
815
|
default:
|
|
771
|
-
throw new
|
|
816
|
+
throw new e.ElectroAttributeValidationError(this.path, `Invalid attribute value supplied to "set" attribute "${this.path}". Received value of type "${valueType}". Set values must be supplied as either Arrays, native JavaScript Set objects, DocumentClient Set objects, strings, or numbers.`)
|
|
772
817
|
}
|
|
773
818
|
|
|
774
819
|
}
|
|
@@ -795,7 +840,9 @@ class SetAttribute extends Attribute {
|
|
|
795
840
|
this._checkGetSet(set, "set");
|
|
796
841
|
const setter = set || ((attr) => attr);
|
|
797
842
|
return (values, siblings) => {
|
|
798
|
-
const results =
|
|
843
|
+
const results = values && values.wrapperName === 'Set'
|
|
844
|
+
? setter(values.values, siblings)
|
|
845
|
+
: setter(values, siblings)
|
|
799
846
|
if (results !== undefined) {
|
|
800
847
|
return this.toDDBSet(results);
|
|
801
848
|
}
|
|
@@ -804,10 +851,14 @@ class SetAttribute extends Attribute {
|
|
|
804
851
|
|
|
805
852
|
_isType(value) {
|
|
806
853
|
if (value === undefined) {
|
|
807
|
-
|
|
854
|
+
const reason = [];
|
|
855
|
+
if (this.required) {
|
|
856
|
+
reason.push(new e.ElectroAttributeValidationError(this.path, `Invalid value type at entity path: "${this.path}". Value is required.`));
|
|
857
|
+
}
|
|
858
|
+
return [!this.required, reason];
|
|
808
859
|
}
|
|
809
860
|
|
|
810
|
-
let reason =
|
|
861
|
+
let reason = [];
|
|
811
862
|
const [childrenAreValid, childErrors] = this._validateChildren(value);
|
|
812
863
|
if (!childrenAreValid) {
|
|
813
864
|
reason = childErrors;
|
|
@@ -817,7 +868,7 @@ class SetAttribute extends Attribute {
|
|
|
817
868
|
|
|
818
869
|
_validateChildren(value) {
|
|
819
870
|
const valueType = getValueType(value);
|
|
820
|
-
|
|
871
|
+
let errors = [];
|
|
821
872
|
let arr = [];
|
|
822
873
|
if (valueType === ValueTypes.array) {
|
|
823
874
|
arr = value;
|
|
@@ -827,16 +878,16 @@ class SetAttribute extends Attribute {
|
|
|
827
878
|
arr = value.values;
|
|
828
879
|
} else {
|
|
829
880
|
errors.push(
|
|
830
|
-
`Invalid value type at attribute path: "${this.path}". Expected value to be an Expected value to be an Array, native JavaScript Set objects, or DocumentClient Set objects to fulfill attribute type "${this.type}"`
|
|
881
|
+
new e.ElectroAttributeValidationError(this.path, `Invalid value type at attribute path: "${this.path}". Expected value to be an Expected value to be an Array, native JavaScript Set objects, or DocumentClient Set objects to fulfill attribute type "${this.type}"`)
|
|
831
882
|
)
|
|
832
883
|
}
|
|
833
884
|
for (const item of arr) {
|
|
834
|
-
const [isValid,
|
|
885
|
+
const [isValid, errorValues] = this.items.isValid(item);
|
|
835
886
|
if (!isValid) {
|
|
836
|
-
errors
|
|
887
|
+
errors = [...errors, ...errorValues];
|
|
837
888
|
}
|
|
838
889
|
}
|
|
839
|
-
return [errors.length === 0, errors
|
|
890
|
+
return [errors.length === 0, errors];
|
|
840
891
|
}
|
|
841
892
|
|
|
842
893
|
val(value) {
|
|
@@ -1232,11 +1283,11 @@ class Schema {
|
|
|
1232
1283
|
for (const path of paths) {
|
|
1233
1284
|
const attribute = this.traverser.getPath(path);
|
|
1234
1285
|
if (!attribute) {
|
|
1235
|
-
throw new
|
|
1286
|
+
throw new e.ElectroAttributeValidationError(path, `Attribute "${path}" does not exist on model.`);
|
|
1236
1287
|
} else if (attribute.readOnly) {
|
|
1237
|
-
throw new
|
|
1288
|
+
throw new e.ElectroAttributeValidationError(attribute.path, `Attribute "${attribute.path}" is Read-Only and cannot be removed`);
|
|
1238
1289
|
} else if (attribute.required) {
|
|
1239
|
-
throw new
|
|
1290
|
+
throw new e.ElectroAttributeValidationError(attribute.path, `Attribute "${attribute.path}" is Required and cannot be removed`);
|
|
1240
1291
|
}
|
|
1241
1292
|
}
|
|
1242
1293
|
return paths;
|
|
@@ -1251,7 +1302,7 @@ class Schema {
|
|
|
1251
1302
|
}
|
|
1252
1303
|
if (attribute.readOnly) {
|
|
1253
1304
|
// todo: #electroerror
|
|
1254
|
-
throw new
|
|
1305
|
+
throw new e.ElectroAttributeValidationError(attribute.path, `Attribute "${attribute.path}" is Read-Only and cannot be updated`);
|
|
1255
1306
|
} else {
|
|
1256
1307
|
record[path] = attribute.getValidate(value);
|
|
1257
1308
|
}
|
package/tsconfig.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
/* Strict Type-Checking Options */
|
|
28
28
|
"strict": true, /* Enable all strict type-checking options. */
|
|
29
|
-
|
|
29
|
+
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
|
30
30
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
|
31
31
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
|
32
32
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|