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/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
- let reason = definition(val);
331
- return [!reason, reason || ""];
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 = isValid ? "" : `Invalid value for attribute "${this.path}": Failed model defined regex`;
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 (val) => [true, ""];
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
- return [!this.required, this.required ? `Invalid value type at entity path: "${this.path}". Value is required.` : ""];
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 = `Invalid value type at entity path: "${this.path}". Value not found in set of acceptable values: ${u.commaSeparatedString(this.enumArray)}`;
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 = `Invalid value type at entity path: "${this.path}". Received value of type "${typeof value}", expected value of type "${this.type}"`;
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, typeError] = this._isType(value);
418
- let [isValid, validationError] = this.validate(value);
419
- let reason = [typeError, validationError].filter(Boolean).join(", ");
420
- return [isTyped && isValid, reason];
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.message];
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, validationError] = this.isValid(value);
454
+ let [isValid, validationErrors] = this.isValid(value);
437
455
  if (!isValid) {
438
- // todo: #electroerror
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
- return [!this.required, this.required ? `Invalid value type at entity path: "${this.path}". Value is required.` : ""];
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
- const errors = [];
550
+ let errors = [];
530
551
  if (valueType === ValueTypes.object) {
531
552
  for (const child of Object.keys(attributes)) {
532
- const [isValid, errorMessages] = attributes[child].isValid(value === undefined ? value : value[child])
553
+ const [isValid, errorValues] = attributes[child].isValid(value === undefined ? value : value[child])
533
554
  if (!isValid) {
534
- errors.push(errorMessages);
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.filter(Boolean).join(", ")];
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 Error(`Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"`);
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
- return [!this.required, this.required ? `Invalid value type at entity path: "${this.path}". Value is required.` : ""];
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, errorMessages] = this.items.isValid(value[i]);
702
+ const [isValid, errorValues] = this.items.isValid(value[i]);
677
703
  if (!isValid) {
678
- errors.push(errorMessages + ` at index "${i}"`);
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.filter(Boolean).join(", ")];
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 Error(`Invalid value type at entity path "${this.path}. Received value of type "${getValueType(value)}", expected value of type "array"`);
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 Error(`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.`)
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 = setter(values, siblings);
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
- return [!this.required, this.required ? `Invalid value type at entity path: "${this.path}". Value is required.` : ""];
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
- const errors = [];
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, errorMessage] = this.items.isValid(item);
885
+ const [isValid, errorValues] = this.items.isValid(item);
835
886
  if (!isValid) {
836
- errors.push(errorMessage);
887
+ errors = [...errors, ...errorValues];
837
888
  }
838
889
  }
839
- return [errors.length === 0, errors.filter(Boolean).join(", ")];
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 Error(`Attribute "${path}" does not exist on model.`);
1286
+ throw new e.ElectroAttributeValidationError(path, `Attribute "${path}" does not exist on model.`);
1236
1287
  } else if (attribute.readOnly) {
1237
- throw new Error(`Attribute "${attribute.path}" is Read-Only and cannot be removed`);
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 Error(`Attribute "${attribute.path}" is Required and cannot be removed`);
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 Error(`Attribute "${attribute.path}" is Read-Only and cannot be updated`);
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
- "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
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. */