@rtpaulino/entity 0.22.0 → 0.23.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/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import 'reflect-metadata';
2
2
  export * from './lib/entity.js';
3
3
  export * from './lib/entity-utils.js';
4
4
  export * from './lib/entity-di.js';
5
+ export * from './lib/entity-registry.js';
5
6
  export * from './lib/types.js';
6
7
  export * from './lib/property.js';
7
8
  export * from './lib/validation-error.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import 'reflect-metadata';
2
2
  export * from './lib/entity.js';
3
3
  export * from './lib/entity-utils.js';
4
4
  export * from './lib/entity-di.js';
5
+ export * from './lib/entity-registry.js';
5
6
  export * from './lib/types.js';
6
7
  export * from './lib/property.js';
7
8
  export * from './lib/validation-error.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import 'reflect-metadata';\n\nexport * from './lib/entity.js';\nexport * from './lib/entity-utils.js';\nexport * from './lib/entity-di.js';\nexport * from './lib/types.js';\nexport * from './lib/property.js';\nexport * from './lib/validation-error.js';\nexport * from './lib/problem.js';\nexport * from './lib/validation-utils.js';\nexport * from './lib/primitive-deserializers.js';\nexport * from './lib/validators.js';\nexport * from './lib/zod-property.js';\nexport * from './lib/injected-property.js';\n"],"names":[],"mappings":"AAAA,OAAO,mBAAmB;AAE1B,cAAc,kBAAkB;AAChC,cAAc,wBAAwB;AACtC,cAAc,qBAAqB;AACnC,cAAc,iBAAiB;AAC/B,cAAc,oBAAoB;AAClC,cAAc,4BAA4B;AAC1C,cAAc,mBAAmB;AACjC,cAAc,4BAA4B;AAC1C,cAAc,mCAAmC;AACjD,cAAc,sBAAsB;AACpC,cAAc,wBAAwB;AACtC,cAAc,6BAA6B"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import 'reflect-metadata';\n\nexport * from './lib/entity.js';\nexport * from './lib/entity-utils.js';\nexport * from './lib/entity-di.js';\nexport * from './lib/entity-registry.js';\nexport * from './lib/types.js';\nexport * from './lib/property.js';\nexport * from './lib/validation-error.js';\nexport * from './lib/problem.js';\nexport * from './lib/validation-utils.js';\nexport * from './lib/primitive-deserializers.js';\nexport * from './lib/validators.js';\nexport * from './lib/zod-property.js';\nexport * from './lib/injected-property.js';\n"],"names":[],"mappings":"AAAA,OAAO,mBAAmB;AAE1B,cAAc,kBAAkB;AAChC,cAAc,wBAAwB;AACtC,cAAc,qBAAqB;AACnC,cAAc,2BAA2B;AACzC,cAAc,iBAAiB;AAC/B,cAAc,oBAAoB;AAClC,cAAc,4BAA4B;AAC1C,cAAc,mBAAmB;AACjC,cAAc,4BAA4B;AAC1C,cAAc,mCAAmC;AACjD,cAAc,sBAAsB;AACpC,cAAc,wBAAwB;AACtC,cAAc,6BAA6B"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Registry for entity classes decorated with @Entity()
3
+ * Stores entity constructors by name for discriminated entity deserialization
4
+ */
5
+ export declare class EntityRegistry {
6
+ private static readonly registry;
7
+ /**
8
+ * Registers an entity class with a name
9
+ * @param name - The name to register the entity under
10
+ * @param entityClass - The entity class constructor
11
+ * @throws Error if an entity with this name is already registered
12
+ */
13
+ static register(name: string, entityClass: Function): void;
14
+ /**
15
+ * Gets an entity class by name
16
+ * @param name - The name of the entity to retrieve
17
+ * @returns The entity class constructor, or undefined if not found
18
+ */
19
+ static get(name: string): Function | undefined;
20
+ /**
21
+ * Checks if an entity with the given name is registered
22
+ * @param name - The name to check
23
+ * @returns true if an entity with this name is registered
24
+ */
25
+ static has(name: string): boolean;
26
+ /**
27
+ * Gets all registered entity names
28
+ * @returns Array of all registered entity names
29
+ */
30
+ static getAllNames(): string[];
31
+ }
32
+ //# sourceMappingURL=entity-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-registry.d.ts","sourceRoot":"","sources":["../../src/lib/entity-registry.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAA+B;IAE/D;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,GAAG,IAAI;IAW1D;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAI9C;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC;;;OAGG;IACH,MAAM,CAAC,WAAW,IAAI,MAAM,EAAE;CAG/B"}
@@ -0,0 +1,42 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-function-type */ /**
2
+ * Registry for entity classes decorated with @Entity()
3
+ * Stores entity constructors by name for discriminated entity deserialization
4
+ */ export class EntityRegistry {
5
+ static{
6
+ this.registry = new Map();
7
+ }
8
+ /**
9
+ * Registers an entity class with a name
10
+ * @param name - The name to register the entity under
11
+ * @param entityClass - The entity class constructor
12
+ * @throws Error if an entity with this name is already registered
13
+ */ static register(name, entityClass) {
14
+ const existing = this.registry.get(name);
15
+ if (existing && existing !== entityClass) {
16
+ throw new Error(`Entity name conflict: An entity with name '${name}' is already registered. ` + `Existing: ${existing.name}, New: ${entityClass.name}`);
17
+ }
18
+ this.registry.set(name, entityClass);
19
+ }
20
+ /**
21
+ * Gets an entity class by name
22
+ * @param name - The name of the entity to retrieve
23
+ * @returns The entity class constructor, or undefined if not found
24
+ */ static get(name) {
25
+ return this.registry.get(name);
26
+ }
27
+ /**
28
+ * Checks if an entity with the given name is registered
29
+ * @param name - The name to check
30
+ * @returns true if an entity with this name is registered
31
+ */ static has(name) {
32
+ return this.registry.has(name);
33
+ }
34
+ /**
35
+ * Gets all registered entity names
36
+ * @returns Array of all registered entity names
37
+ */ static getAllNames() {
38
+ return Array.from(this.registry.keys());
39
+ }
40
+ }
41
+
42
+ //# sourceMappingURL=entity-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/entity-registry.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-unsafe-function-type */\n\n/**\n * Registry for entity classes decorated with @Entity()\n * Stores entity constructors by name for discriminated entity deserialization\n */\nexport class EntityRegistry {\n private static readonly registry = new Map<string, Function>();\n\n /**\n * Registers an entity class with a name\n * @param name - The name to register the entity under\n * @param entityClass - The entity class constructor\n * @throws Error if an entity with this name is already registered\n */\n static register(name: string, entityClass: Function): void {\n const existing = this.registry.get(name);\n if (existing && existing !== entityClass) {\n throw new Error(\n `Entity name conflict: An entity with name '${name}' is already registered. ` +\n `Existing: ${existing.name}, New: ${entityClass.name}`,\n );\n }\n this.registry.set(name, entityClass);\n }\n\n /**\n * Gets an entity class by name\n * @param name - The name of the entity to retrieve\n * @returns The entity class constructor, or undefined if not found\n */\n static get(name: string): Function | undefined {\n return this.registry.get(name);\n }\n\n /**\n * Checks if an entity with the given name is registered\n * @param name - The name to check\n * @returns true if an entity with this name is registered\n */\n static has(name: string): boolean {\n return this.registry.has(name);\n }\n\n /**\n * Gets all registered entity names\n * @returns Array of all registered entity names\n */\n static getAllNames(): string[] {\n return Array.from(this.registry.keys());\n }\n}\n"],"names":["EntityRegistry","registry","Map","register","name","entityClass","existing","get","Error","set","has","getAllNames","Array","from","keys"],"mappings":"AAAA,qDAAqD,GACrD,6DAA6D,GAE7D;;;CAGC,GACD,OAAO,MAAMA;;aACaC,WAAW,IAAIC;;IAEvC;;;;;GAKC,GACD,OAAOC,SAASC,IAAY,EAAEC,WAAqB,EAAQ;QACzD,MAAMC,WAAW,IAAI,CAACL,QAAQ,CAACM,GAAG,CAACH;QACnC,IAAIE,YAAYA,aAAaD,aAAa;YACxC,MAAM,IAAIG,MACR,CAAC,2CAA2C,EAAEJ,KAAK,yBAAyB,CAAC,GAC3E,CAAC,UAAU,EAAEE,SAASF,IAAI,CAAC,OAAO,EAAEC,YAAYD,IAAI,EAAE;QAE5D;QACA,IAAI,CAACH,QAAQ,CAACQ,GAAG,CAACL,MAAMC;IAC1B;IAEA;;;;GAIC,GACD,OAAOE,IAAIH,IAAY,EAAwB;QAC7C,OAAO,IAAI,CAACH,QAAQ,CAACM,GAAG,CAACH;IAC3B;IAEA;;;;GAIC,GACD,OAAOM,IAAIN,IAAY,EAAW;QAChC,OAAO,IAAI,CAACH,QAAQ,CAACS,GAAG,CAACN;IAC3B;IAEA;;;GAGC,GACD,OAAOO,cAAwB;QAC7B,OAAOC,MAAMC,IAAI,CAAC,IAAI,CAACZ,QAAQ,CAACa,IAAI;IACtC;AACF"}
@@ -30,6 +30,24 @@ export declare class EntityUtils {
30
30
  * @private
31
31
  */
32
32
  private static getEntityOptions;
33
+ /**
34
+ * Gets the registered name for an entity class or instance
35
+ *
36
+ * @param entityOrClass - The entity class constructor or instance
37
+ * @returns The entity name, or undefined if not found
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * @Entity({ name: 'CustomUser' })
42
+ * class User {
43
+ * name: string;
44
+ * }
45
+ *
46
+ * console.log(EntityUtils.getEntityName(User)); // 'CustomUser'
47
+ * console.log(EntityUtils.getEntityName(new User())); // 'CustomUser'
48
+ * ```
49
+ */
50
+ static getEntityName(entityOrClass: unknown): string | undefined;
33
51
  /**
34
52
  * Checks if a given entity is marked as a collection entity
35
53
  *
@@ -70,6 +88,15 @@ export declare class EntityUtils {
70
88
  * ```
71
89
  */
72
90
  static isStringifiable(entityOrClass: unknown): boolean;
91
+ /**
92
+ * Gets the "wrapper" property name for entities that act as transparent wrappers.
93
+ * When set, this property name is excluded from error paths during validation.
94
+ *
95
+ * @param entityOrClass - The entity instance or class to check
96
+ * @returns The wrapper property name, or undefined if not a wrapper entity
97
+ * @private
98
+ */
99
+ private static getWrapperPropertyName;
73
100
  static sameEntity(a: object, b: object): boolean;
74
101
  static getPropertyKeys(target: object): string[];
75
102
  static getPropertyOptions(target: object, propertyKey: string): PropertyOptions | undefined;
@@ -407,12 +434,22 @@ export declare class EntityUtils {
407
434
  * @private
408
435
  */
409
436
  private static deserializeSingleValue;
437
+ /**
438
+ * Deserializes a discriminated entity value by reading the discriminator and looking up the entity class
439
+ * @private
440
+ */
441
+ private static deserializeDiscriminatedValue;
410
442
  /**
411
443
  * Validates a property value by running validators and nested entity validation.
412
444
  * Prepends the property path to all returned problems.
413
445
  * @private
414
446
  */
415
447
  private static validatePropertyValue;
448
+ /**
449
+ * Checks if an array contains any entity elements
450
+ * @private
451
+ */
452
+ private static hasEntityElements;
416
453
  /**
417
454
  * Runs property validators for a given property value
418
455
  * @private
@@ -1 +1 @@
1
- {"version":3,"file":"entity-utils.d.ts","sourceRoot":"","sources":["../../src/lib/entity-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"entity-utils.d.ts","sourceRoot":"","sources":["../../src/lib/entity-utils.ts"],"names":[],"mappings":"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"}
@@ -4,9 +4,10 @@ import { EntityDI } from './entity-di.js';
4
4
  import { isEqualWith } from 'lodash-es';
5
5
  import { ValidationError } from './validation-error.js';
6
6
  import { Problem } from './problem.js';
7
- import { prependPropertyPath, prependArrayIndex, createValidationError, combinePropertyPaths } from './validation-utils.js';
7
+ import { prependArrayIndex, prependPropertyPath, createValidationError } from './validation-utils.js';
8
8
  import { isPrimitiveConstructor, deserializePrimitive } from './primitive-deserializers.js';
9
9
  import { ok } from 'assert';
10
+ import { EntityRegistry } from './entity-registry.js';
10
11
  /**
11
12
  * WeakMap to store validation problems for entity instances
12
13
  */ const problemsStorage = new WeakMap();
@@ -60,6 +61,29 @@ export class EntityUtils {
60
61
  return options ?? {};
61
62
  }
62
63
  /**
64
+ * Gets the registered name for an entity class or instance
65
+ *
66
+ * @param entityOrClass - The entity class constructor or instance
67
+ * @returns The entity name, or undefined if not found
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * @Entity({ name: 'CustomUser' })
72
+ * class User {
73
+ * name: string;
74
+ * }
75
+ *
76
+ * console.log(EntityUtils.getEntityName(User)); // 'CustomUser'
77
+ * console.log(EntityUtils.getEntityName(new User())); // 'CustomUser'
78
+ * ```
79
+ */ static getEntityName(entityOrClass) {
80
+ if (!this.isEntity(entityOrClass)) {
81
+ return undefined;
82
+ }
83
+ const options = this.getEntityOptions(entityOrClass);
84
+ return options.name;
85
+ }
86
+ /**
63
87
  * Checks if a given entity is marked as a collection entity
64
88
  *
65
89
  * @param entityOrClass - The entity instance or class to check
@@ -109,6 +133,19 @@ export class EntityUtils {
109
133
  const options = this.getEntityOptions(entityOrClass);
110
134
  return options.stringifiable === true;
111
135
  }
136
+ /**
137
+ * Gets the "wrapper" property name for entities that act as transparent wrappers.
138
+ * When set, this property name is excluded from error paths during validation.
139
+ *
140
+ * @param entityOrClass - The entity instance or class to check
141
+ * @returns The wrapper property name, or undefined if not a wrapper entity
142
+ * @private
143
+ */ static getWrapperPropertyName(entityOrClass) {
144
+ if (!this.isEntity(entityOrClass)) {
145
+ return undefined;
146
+ }
147
+ return this.getEntityOptions(entityOrClass).wrapperProperty;
148
+ }
112
149
  static sameEntity(a, b) {
113
150
  if (!this.isEntity(a) || !this.isEntity(b)) {
114
151
  return false;
@@ -334,7 +371,7 @@ export class EntityUtils {
334
371
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
335
372
  return value.map((item)=>options.serialize(item));
336
373
  }
337
- return value.map((item)=>this.serializeValue(item));
374
+ return value.map((item)=>this.serializeValue(item, options));
338
375
  }
339
376
  if (options?.serialize) {
340
377
  return options.serialize(value);
@@ -346,7 +383,25 @@ export class EntityUtils {
346
383
  return value.toString();
347
384
  }
348
385
  if (this.isEntity(value)) {
349
- return this.toJSON(value);
386
+ const serialized = this.toJSON(value);
387
+ // If this is a discriminated entity property, add the discriminator inline
388
+ if (options?.discriminated === true) {
389
+ const discriminatorProperty = options.discriminatorProperty;
390
+ ok(discriminatorProperty, 'Discriminator property must be defined');
391
+ const entityClass = Object.getPrototypeOf(value).constructor;
392
+ const entityName = this.getEntityName(entityClass);
393
+ if (!entityName) {
394
+ throw new Error(`Cannot serialize discriminated entity: Entity class '${entityClass.name}' is not registered. Ensure it's decorated with @Entity().`);
395
+ }
396
+ if (typeof serialized !== 'object' || Array.isArray(serialized) || serialized === null) {
397
+ throw new Error(`Cannot serialize discriminated entity: Expected serialized value to be an object.`);
398
+ }
399
+ return {
400
+ ...serialized,
401
+ [discriminatorProperty]: entityName
402
+ };
403
+ }
404
+ return serialized;
350
405
  }
351
406
  if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
352
407
  return value;
@@ -357,13 +412,10 @@ export class EntityUtils {
357
412
  * Internal parse implementation with extended options
358
413
  * @private
359
414
  */ static async _parseInternal(entityClass, plainObject, options = {}) {
360
- if (this.isStringifiable(entityClass)) {
361
- plainObject = {
362
- value: plainObject
363
- };
364
- } else if (this.isCollectionEntity(entityClass)) {
415
+ const wrapperPropertyName = this.getWrapperPropertyName(entityClass);
416
+ if (wrapperPropertyName) {
365
417
  plainObject = {
366
- collection: plainObject
418
+ [wrapperPropertyName]: plainObject
367
419
  };
368
420
  }
369
421
  if (plainObject == null) {
@@ -420,11 +472,16 @@ export class EntityUtils {
420
472
  });
421
473
  } catch (error) {
422
474
  if (error instanceof ValidationError) {
423
- const problems = prependPropertyPath(key, error);
424
- hardProblems.push(...problems);
475
+ const isWrapperProperty = wrapperPropertyName === key;
476
+ if (isWrapperProperty) {
477
+ hardProblems.push(...error.problems);
478
+ } else {
479
+ const problems = prependPropertyPath(key, error.problems);
480
+ hardProblems.push(...problems);
481
+ }
425
482
  } else if (error instanceof Error) {
426
483
  hardProblems.push(new Problem({
427
- property: key,
484
+ property: wrapperPropertyName === key ? '' : key,
428
485
  message: error.message
429
486
  }));
430
487
  } else {
@@ -792,10 +849,9 @@ export class EntityUtils {
792
849
  * Deserializes a single value according to the type metadata
793
850
  * @private
794
851
  */ static async deserializeValue(value, options, parseOptions) {
795
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
796
- const typeConstructor = options.type();
797
852
  const isArray = options.array === true;
798
853
  const isSparse = options.sparse === true;
854
+ const isDiscriminated = options.discriminated === true;
799
855
  if (isArray) {
800
856
  if (!Array.isArray(value)) {
801
857
  throw createValidationError(`Expects an array but received ${typeof value}`);
@@ -816,7 +872,11 @@ export class EntityUtils {
816
872
  try {
817
873
  if (options.deserialize) {
818
874
  result.push(options.deserialize(item));
875
+ } else if (isDiscriminated) {
876
+ result.push(await this.deserializeDiscriminatedValue(item, options));
819
877
  } else {
878
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
879
+ const typeConstructor = options.type();
820
880
  result.push(await this.deserializeSingleValue(item, typeConstructor, parseOptions));
821
881
  }
822
882
  } catch (error) {
@@ -837,6 +897,11 @@ export class EntityUtils {
837
897
  if (options.deserialize) {
838
898
  return options.deserialize(value);
839
899
  }
900
+ if (isDiscriminated) {
901
+ return await this.deserializeDiscriminatedValue(value, options);
902
+ }
903
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
904
+ const typeConstructor = options.type();
840
905
  return await this.deserializeSingleValue(value, typeConstructor, parseOptions);
841
906
  }
842
907
  /**
@@ -853,6 +918,28 @@ export class EntityUtils {
853
918
  throw createValidationError(`Has unknown type constructor. Supported types are: String, Number, Boolean, Date, BigInt, and @Entity() classes. Use passthrough: true to explicitly allow unknown types.`);
854
919
  }
855
920
  /**
921
+ * Deserializes a discriminated entity value by reading the discriminator and looking up the entity class
922
+ * @private
923
+ */ static async deserializeDiscriminatedValue(value, options) {
924
+ if (value == null) {
925
+ throw createValidationError(`Cannot deserialize discriminated entity from null or undefined`);
926
+ }
927
+ if (typeof value !== 'object' || Array.isArray(value)) {
928
+ throw createValidationError(`Discriminated entity must be an object, received ${typeof value}`);
929
+ }
930
+ const discriminatorProperty = options.discriminatorProperty;
931
+ ok(discriminatorProperty, 'Discriminator property must be defined');
932
+ const discriminatorValue = value[discriminatorProperty];
933
+ if (typeof discriminatorValue !== 'string' || discriminatorValue.trim() === '') {
934
+ throw createValidationError(`Missing or invalid discriminator property '${discriminatorProperty}'. Expected a non-empty string.`);
935
+ }
936
+ const entityClass = EntityRegistry.get(discriminatorValue);
937
+ if (!entityClass) {
938
+ throw createValidationError(`Unknown entity type '${discriminatorValue}'. No entity registered with this name.`);
939
+ }
940
+ return await this.parse(entityClass, value, {});
941
+ }
942
+ /**
856
943
  * Validates a property value by running validators and nested entity validation.
857
944
  * Prepends the property path to all returned problems.
858
945
  * @private
@@ -863,24 +950,32 @@ export class EntityUtils {
863
950
  const validatorProblems = await validator({
864
951
  value
865
952
  });
866
- // Prepend propertyPath to all problems
867
- for (const problem of validatorProblems){
868
- problems.push(new Problem({
869
- property: combinePropertyPaths(propertyPath, problem.property),
870
- message: problem.message
871
- }));
953
+ if (validatorProblems.length > 0) {
954
+ const prepended = prependPropertyPath(propertyPath, validatorProblems);
955
+ problems.push(...prepended);
872
956
  }
873
957
  }
874
958
  }
875
959
  if (EntityUtils.isEntity(value)) {
876
960
  const existingProblems = problemsStorage.get(value);
877
961
  const nestedProblems = existingProblems && existingProblems.length > 0 ? existingProblems : await EntityUtils.validate(value);
878
- const prependedProblems = prependPropertyPath(propertyPath, new ValidationError(nestedProblems));
879
- problems.push(...prependedProblems);
962
+ if (nestedProblems.length > 0) {
963
+ const prepended = prependPropertyPath(propertyPath, nestedProblems);
964
+ problems.push(...prepended);
965
+ }
880
966
  }
881
967
  return problems;
882
968
  }
883
969
  /**
970
+ * Checks if an array contains any entity elements
971
+ * @private
972
+ */ static hasEntityElements(value) {
973
+ if (!Array.isArray(value)) {
974
+ return false;
975
+ }
976
+ return value.some((element)=>this.isEntity(element));
977
+ }
978
+ /**
884
979
  * Runs property validators for a given property value
885
980
  * @private
886
981
  */ static async runPropertyValidators(key, value, options) {
@@ -897,15 +992,13 @@ export class EntityUtils {
897
992
  const validatorProblems = await validator({
898
993
  value
899
994
  });
900
- for (const problem of validatorProblems){
901
- problems.push(new Problem({
902
- property: combinePropertyPaths(key, problem.property),
903
- message: problem.message
904
- }));
995
+ if (validatorProblems.length > 0) {
996
+ const prepended = prependPropertyPath(key, validatorProblems);
997
+ problems.push(...prepended);
905
998
  }
906
999
  }
907
1000
  const validators = options.validators || [];
908
- if (validators.length > 0) {
1001
+ if (validators.length > 0 || this.hasEntityElements(value)) {
909
1002
  for(let i = 0; i < value.length; i++){
910
1003
  const element = value[i];
911
1004
  if (element !== null && element !== undefined) {
@@ -924,12 +1017,14 @@ export class EntityUtils {
924
1017
  */ static async validateProperties(dataOrInstance, prototype) {
925
1018
  const problems = [];
926
1019
  const keys = Object.keys(dataOrInstance);
1020
+ const wrapperProperty = this.getWrapperPropertyName(dataOrInstance);
927
1021
  for (const key of keys){
928
1022
  const options = this.getPropertyOptions(prototype, key);
929
1023
  if (options) {
930
1024
  const value = dataOrInstance[key];
931
1025
  if (value != null) {
932
- const validationProblems = await this.runPropertyValidators(key, value, options);
1026
+ const propertyPath = wrapperProperty === key ? '' : key;
1027
+ const validationProblems = await this.runPropertyValidators(propertyPath, value, options);
933
1028
  problems.push(...validationProblems);
934
1029
  }
935
1030
  }