@rtpaulino/entity 0.18.0 → 0.20.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.
@@ -1,4 +1,4 @@
1
- import { PropertyOptions, SafeOperationResult } from './types.js';
1
+ import { ParseOptions, PropertyOptions, SafeOperationResult } from './types.js';
2
2
  import { Problem } from './problem.js';
3
3
  export declare class EntityUtils {
4
4
  /**
@@ -22,6 +22,34 @@ export declare class EntityUtils {
22
22
  * ```
23
23
  */
24
24
  static isEntity(obj: unknown): obj is object;
25
+ /**
26
+ * Gets the entity options for a given constructor
27
+ *
28
+ * @param entityOrClass - The entity class constructor or instance
29
+ * @returns EntityOptions object (empty object if no options are defined)
30
+ * @private
31
+ */
32
+ private static getEntityOptions;
33
+ /**
34
+ * Checks if a given entity is marked as a collection entity
35
+ *
36
+ * @param entityOrClass - The entity instance or class to check
37
+ * @returns true if the entity is a collection entity, false otherwise
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * @CollectionEntity()
42
+ * class Tags {
43
+ * @ArrayProperty(() => String)
44
+ * collection: string[];
45
+ * }
46
+ *
47
+ * const tags = new Tags({ collection: ['a', 'b'] });
48
+ * console.log(EntityUtils.isCollectionEntity(tags)); // true
49
+ * console.log(EntityUtils.isCollectionEntity(Tags)); // true
50
+ * ```
51
+ */
52
+ static isCollectionEntity(entityOrClass: unknown): boolean;
25
53
  static sameEntity(a: object, b: object): boolean;
26
54
  static getPropertyKeys(target: object): string[];
27
55
  static getPropertyOptions(target: object, propertyKey: string): PropertyOptions | undefined;
@@ -36,7 +64,7 @@ export declare class EntityUtils {
36
64
  * Serializes an entity to a plain object, converting only properties decorated with @Property()
37
65
  *
38
66
  * @param entity - The entity instance to serialize
39
- * @returns A plain object containing only the serialized decorated properties
67
+ * @returns A plain object containing only the serialized decorated properties, or an array for collection entities
40
68
  *
41
69
  * @remarks
42
70
  * Serialization rules:
@@ -49,6 +77,7 @@ export declare class EntityUtils {
49
77
  * - undefined values are excluded from the output
50
78
  * - null values are included in the output
51
79
  * - Circular references are not supported (will cause stack overflow)
80
+ * - Collection entities (@CollectionEntity) are unwrapped to just their array
52
81
  *
53
82
  * @example
54
83
  * ```typescript
@@ -80,9 +109,19 @@ export declare class EntityUtils {
80
109
  * // address: { street: '123 Main St', city: 'Boston' },
81
110
  * // createdAt: '2024-01-01T00:00:00.000Z'
82
111
  * // }
112
+ *
113
+ * @CollectionEntity()
114
+ * class Tags {
115
+ * @ArrayProperty(() => String)
116
+ * collection: string[];
117
+ * }
118
+ *
119
+ * const tags = new Tags({ collection: ['a', 'b'] });
120
+ * const json = EntityUtils.toJSON(tags);
121
+ * // ['a', 'b'] - unwrapped to array
83
122
  * ```
84
123
  */
85
- static toJSON<T extends object>(entity: T): Record<string, unknown>;
124
+ static toJSON<T extends object>(entity: T): unknown;
86
125
  /**
87
126
  * Serializes a single value according to the toJSON rules
88
127
  * @private
@@ -93,7 +132,7 @@ export declare class EntityUtils {
93
132
  *
94
133
  * @param entityClass - The entity class constructor. Must accept a data object parameter.
95
134
  * @param plainObject - The plain object to deserialize
96
- * @param options - Parse options (strict mode)
135
+ * @param parseOptions - Parse options (strict mode)
97
136
  * @returns Promise resolving to a new instance of the entity with deserialized values
98
137
  *
99
138
  * @remarks
@@ -132,15 +171,13 @@ export declare class EntityUtils {
132
171
  * const userStrict = await EntityUtils.parse(User, json, { strict: true });
133
172
  * ```
134
173
  */
135
- static parse<T extends object>(entityClass: new (data: any) => T, plainObject: unknown, options?: {
136
- strict?: boolean;
137
- }): Promise<T>;
174
+ static parse<T extends object>(entityClass: new (data: any) => T, plainObject: unknown, parseOptions?: ParseOptions): Promise<T>;
138
175
  /**
139
176
  * Safely deserializes a plain object to an entity instance without throwing errors
140
177
  *
141
178
  * @param entityClass - The entity class constructor. Must accept a data object parameter.
142
179
  * @param plainObject - The plain object to deserialize
143
- * @param options - Parse options (strict mode)
180
+ * @param parseOptions - Parse options (strict mode)
144
181
  * @returns Promise resolving to a result object with success flag, data, and problems
145
182
  *
146
183
  * @remarks
@@ -173,9 +210,7 @@ export declare class EntityUtils {
173
210
  * }
174
211
  * ```
175
212
  */
176
- static safeParse<T extends object>(entityClass: new (data: any) => T, plainObject: unknown, options?: {
177
- strict?: boolean;
178
- }): SafeOperationResult<T>;
213
+ static safeParse<T extends object>(entityClass: new (data: any) => T, plainObject: unknown, parseOptions?: ParseOptions): SafeOperationResult<T>;
179
214
  /**
180
215
  * Updates an entity instance with new values, respecting preventUpdates flags on properties
181
216
  *
@@ -350,7 +385,7 @@ export declare class EntityUtils {
350
385
  * console.log(rawInput); // { name: 'John', age: 30 }
351
386
  * ```
352
387
  */
353
- static getRawInput<T extends object>(instance: T): Record<string, unknown> | undefined;
388
+ static getRawInput<T extends object>(instance: T): unknown;
354
389
  /**
355
390
  * Sets the raw input data for an entity instance
356
391
  *
@@ -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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"}
@@ -1,4 +1,4 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */ import { ENTITY_METADATA_KEY, ENTITY_VALIDATOR_METADATA_KEY, PROPERTY_METADATA_KEY, PROPERTY_OPTIONS_METADATA_KEY } from './types.js';
1
+ /* eslint-disable @typescript-eslint/no-unsafe-function-type */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ENTITY_METADATA_KEY, ENTITY_OPTIONS_METADATA_KEY, ENTITY_VALIDATOR_METADATA_KEY, PROPERTY_METADATA_KEY, PROPERTY_OPTIONS_METADATA_KEY } from './types.js';
2
2
  import { getInjectedPropertyNames, getInjectedPropertyOptions } from './injected-property.js';
3
3
  import { EntityDI } from './entity-di.js';
4
4
  import { isEqualWith } from 'lodash-es';
@@ -48,6 +48,42 @@ export class EntityUtils {
48
48
  const constructor = Object.getPrototypeOf(obj).constructor;
49
49
  return Reflect.hasMetadata(ENTITY_METADATA_KEY, constructor);
50
50
  }
51
+ /**
52
+ * Gets the entity options for a given constructor
53
+ *
54
+ * @param entityOrClass - The entity class constructor or instance
55
+ * @returns EntityOptions object (empty object if no options are defined)
56
+ * @private
57
+ */ static getEntityOptions(entityOrClass) {
58
+ const constructor = typeof entityOrClass === 'function' ? entityOrClass : Object.getPrototypeOf(entityOrClass).constructor;
59
+ const options = Reflect.getMetadata(ENTITY_OPTIONS_METADATA_KEY, constructor);
60
+ return options ?? {};
61
+ }
62
+ /**
63
+ * Checks if a given entity is marked as a collection entity
64
+ *
65
+ * @param entityOrClass - The entity instance or class to check
66
+ * @returns true if the entity is a collection entity, false otherwise
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * @CollectionEntity()
71
+ * class Tags {
72
+ * @ArrayProperty(() => String)
73
+ * collection: string[];
74
+ * }
75
+ *
76
+ * const tags = new Tags({ collection: ['a', 'b'] });
77
+ * console.log(EntityUtils.isCollectionEntity(tags)); // true
78
+ * console.log(EntityUtils.isCollectionEntity(Tags)); // true
79
+ * ```
80
+ */ static isCollectionEntity(entityOrClass) {
81
+ if (!this.isEntity(entityOrClass)) {
82
+ return false;
83
+ }
84
+ const options = this.getEntityOptions(entityOrClass);
85
+ return options.collection === true;
86
+ }
51
87
  static sameEntity(a, b) {
52
88
  if (!this.isEntity(a) || !this.isEntity(b)) {
53
89
  return false;
@@ -161,7 +197,7 @@ export class EntityUtils {
161
197
  * Serializes an entity to a plain object, converting only properties decorated with @Property()
162
198
  *
163
199
  * @param entity - The entity instance to serialize
164
- * @returns A plain object containing only the serialized decorated properties
200
+ * @returns A plain object containing only the serialized decorated properties, or an array for collection entities
165
201
  *
166
202
  * @remarks
167
203
  * Serialization rules:
@@ -174,6 +210,7 @@ export class EntityUtils {
174
210
  * - undefined values are excluded from the output
175
211
  * - null values are included in the output
176
212
  * - Circular references are not supported (will cause stack overflow)
213
+ * - Collection entities (@CollectionEntity) are unwrapped to just their array
177
214
  *
178
215
  * @example
179
216
  * ```typescript
@@ -205,8 +242,28 @@ export class EntityUtils {
205
242
  * // address: { street: '123 Main St', city: 'Boston' },
206
243
  * // createdAt: '2024-01-01T00:00:00.000Z'
207
244
  * // }
245
+ *
246
+ * @CollectionEntity()
247
+ * class Tags {
248
+ * @ArrayProperty(() => String)
249
+ * collection: string[];
250
+ * }
251
+ *
252
+ * const tags = new Tags({ collection: ['a', 'b'] });
253
+ * const json = EntityUtils.toJSON(tags);
254
+ * // ['a', 'b'] - unwrapped to array
208
255
  * ```
209
256
  */ static toJSON(entity) {
257
+ if (this.isCollectionEntity(entity)) {
258
+ const collectionPropertyOptions = this.getPropertyOptions(entity, 'collection');
259
+ if (!collectionPropertyOptions) {
260
+ throw new Error(`Collection entity 'collection' property is missing metadata`);
261
+ }
262
+ if (!collectionPropertyOptions.array) {
263
+ throw new Error(`Collection entity 'collection' property must be an array`);
264
+ }
265
+ return this.serializeValue(entity.collection, collectionPropertyOptions);
266
+ }
210
267
  const result = {};
211
268
  const keys = this.getPropertyKeys(entity);
212
269
  for (const key of keys){
@@ -263,7 +320,7 @@ export class EntityUtils {
263
320
  *
264
321
  * @param entityClass - The entity class constructor. Must accept a data object parameter.
265
322
  * @param plainObject - The plain object to deserialize
266
- * @param options - Parse options (strict mode)
323
+ * @param parseOptions - Parse options (strict mode)
267
324
  * @returns Promise resolving to a new instance of the entity with deserialized values
268
325
  *
269
326
  * @remarks
@@ -301,7 +358,12 @@ export class EntityUtils {
301
358
  * const user = await EntityUtils.parse(User, json);
302
359
  * const userStrict = await EntityUtils.parse(User, json, { strict: true });
303
360
  * ```
304
- */ static async parse(entityClass, plainObject, options) {
361
+ */ static async parse(entityClass, plainObject, parseOptions = {}) {
362
+ if (this.isCollectionEntity(entityClass)) {
363
+ plainObject = {
364
+ collection: plainObject
365
+ };
366
+ }
305
367
  if (plainObject == null) {
306
368
  throw createValidationError(`Expects an object but received ${typeof plainObject}`);
307
369
  }
@@ -311,7 +373,7 @@ export class EntityUtils {
311
373
  if (typeof plainObject !== 'object') {
312
374
  throw createValidationError(`Expects an object but received ${typeof plainObject}`);
313
375
  }
314
- const strict = options?.strict ?? false;
376
+ const strict = parseOptions?.strict ?? false;
315
377
  const keys = this.getPropertyKeys(entityClass.prototype);
316
378
  const data = {};
317
379
  const hardProblems = [];
@@ -330,27 +392,22 @@ export class EntityUtils {
330
392
  continue;
331
393
  }
332
394
  const isOptional = propertyOptions.optional === true;
333
- if (!(key in plainObject)) {
334
- if (!isOptional) {
335
- hardProblems.push(new Problem({
336
- property: key,
337
- message: 'Required property is missing from input'
338
- }));
395
+ if (!(key in plainObject) || value == null) {
396
+ let valueToSet = value;
397
+ if (propertyOptions.default !== undefined) {
398
+ valueToSet = typeof propertyOptions.default === 'function' ? await propertyOptions.default() : propertyOptions.default;
339
399
  }
340
- continue;
341
- }
342
- if (value === null || value === undefined) {
343
- if (!isOptional) {
400
+ if (!isOptional && valueToSet == null) {
344
401
  hardProblems.push(new Problem({
345
402
  property: key,
346
- message: 'Cannot be null or undefined'
403
+ message: 'Required property is missing, null or undefined from input'
347
404
  }));
348
405
  }
349
- data[key] = value;
406
+ data[key] = valueToSet;
350
407
  continue;
351
408
  }
352
409
  try {
353
- data[key] = await this.deserializeValue(value, propertyOptions);
410
+ data[key] = await this.deserializeValue(value, propertyOptions, parseOptions);
354
411
  } catch (error) {
355
412
  if (error instanceof ValidationError) {
356
413
  const problems = prependPropertyPath(key, error);
@@ -382,7 +439,7 @@ export class EntityUtils {
382
439
  *
383
440
  * @param entityClass - The entity class constructor. Must accept a data object parameter.
384
441
  * @param plainObject - The plain object to deserialize
385
- * @param options - Parse options (strict mode)
442
+ * @param parseOptions - Parse options (strict mode)
386
443
  * @returns Promise resolving to a result object with success flag, data, and problems
387
444
  *
388
445
  * @remarks
@@ -414,9 +471,9 @@ export class EntityUtils {
414
471
  * console.log(result.problems); // Hard problems
415
472
  * }
416
473
  * ```
417
- */ static async safeParse(entityClass, plainObject, options) {
474
+ */ static async safeParse(entityClass, plainObject, parseOptions) {
418
475
  try {
419
- const data = await this.parse(entityClass, plainObject, options);
476
+ const data = await this.parse(entityClass, plainObject, parseOptions);
420
477
  const problems = this.getProblems(data);
421
478
  return {
422
479
  success: true,
@@ -558,7 +615,7 @@ export class EntityUtils {
558
615
  /**
559
616
  * Deserializes a single value according to the type metadata
560
617
  * @private
561
- */ static async deserializeValue(value, options) {
618
+ */ static async deserializeValue(value, options, parseOptions) {
562
619
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
563
620
  const typeConstructor = options.type();
564
621
  const isArray = options.array === true;
@@ -584,7 +641,7 @@ export class EntityUtils {
584
641
  if (options.deserialize) {
585
642
  result.push(options.deserialize(item));
586
643
  } else {
587
- result.push(await this.deserializeSingleValue(item, typeConstructor));
644
+ result.push(await this.deserializeSingleValue(item, typeConstructor, parseOptions));
588
645
  }
589
646
  } catch (error) {
590
647
  if (error instanceof ValidationError) {
@@ -604,21 +661,18 @@ export class EntityUtils {
604
661
  if (options.deserialize) {
605
662
  return options.deserialize(value);
606
663
  }
607
- return await this.deserializeSingleValue(value, typeConstructor);
664
+ return await this.deserializeSingleValue(value, typeConstructor, parseOptions);
608
665
  }
609
666
  /**
610
667
  * Deserializes a single non-array value
611
668
  * Reports validation errors with empty property (caller will prepend context)
612
669
  * @private
613
- */ static async deserializeSingleValue(value, typeConstructor) {
670
+ */ static async deserializeSingleValue(value, typeConstructor, parseOptions) {
614
671
  if (isPrimitiveConstructor(typeConstructor)) {
615
672
  return deserializePrimitive(value, typeConstructor);
616
673
  }
617
674
  if (this.isEntity(typeConstructor)) {
618
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
619
- throw createValidationError(`Expects an object but received ${typeof value}`);
620
- }
621
- return await this.parse(typeConstructor, value);
675
+ return await this.parse(typeConstructor, value, parseOptions);
622
676
  }
623
677
  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.`);
624
678
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/entity-utils.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n ENTITY_METADATA_KEY,\n ENTITY_VALIDATOR_METADATA_KEY,\n PROPERTY_METADATA_KEY,\n PROPERTY_OPTIONS_METADATA_KEY,\n PropertyOptions,\n SafeOperationResult,\n} from './types.js';\nimport {\n getInjectedPropertyNames,\n getInjectedPropertyOptions,\n} from './injected-property.js';\nimport { EntityDI } from './entity-di.js';\nimport { isEqualWith } from 'lodash-es';\nimport { ValidationError } from './validation-error.js';\nimport { Problem } from './problem.js';\nimport {\n prependPropertyPath,\n prependArrayIndex,\n createValidationError,\n combinePropertyPaths,\n} from './validation-utils.js';\nimport {\n isPrimitiveConstructor,\n deserializePrimitive,\n} from './primitive-deserializers.js';\nimport { ok } from 'assert';\n\n/**\n * WeakMap to store validation problems for entity instances\n */\nconst problemsStorage = new WeakMap<object, Problem[]>();\n\n/**\n * WeakMap to store raw input data for entity instances\n */\nconst rawInputStorage = new WeakMap<object, Record<string, unknown>>();\n\nexport class EntityUtils {\n /**\n * Checks if a given object is an instance of a class decorated with @Entity()\n * or if the provided value is an entity class itself\n *\n * @param obj - The object or class to check\n * @returns true if the object is an entity instance or entity class, false otherwise\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * name: string;\n * }\n *\n * const user = new User();\n * console.log(EntityUtils.isEntity(user)); // true\n * console.log(EntityUtils.isEntity(User)); // true\n * console.log(EntityUtils.isEntity({})); // false\n * ```\n */\n static isEntity(obj: unknown): obj is object {\n if (obj == null) {\n return false;\n }\n\n // Check if obj is a constructor function (class)\n if (typeof obj === 'function') {\n return Reflect.hasMetadata(ENTITY_METADATA_KEY, obj);\n }\n\n // Check if obj is an object instance\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n return false;\n }\n\n const constructor = Object.getPrototypeOf(obj).constructor;\n return Reflect.hasMetadata(ENTITY_METADATA_KEY, constructor);\n }\n\n static sameEntity(a: object, b: object): boolean {\n if (!this.isEntity(a) || !this.isEntity(b)) {\n return false;\n }\n\n return Object.getPrototypeOf(a) === Object.getPrototypeOf(b);\n }\n\n static getPropertyKeys(target: object): string[] {\n // Determine if we're dealing with a prototype or an instance\n let currentProto: any;\n\n // Check if target is a prototype by checking if it has a constructor property\n // and if target === target.constructor.prototype\n if (target.constructor && target === target.constructor.prototype) {\n // target is already a prototype\n currentProto = target;\n } else {\n // target is an instance, get its prototype\n currentProto = Object.getPrototypeOf(target);\n }\n\n const keys: string[] = [];\n const seen = new Set<string>();\n\n // Walk the prototype chain to collect all inherited properties\n while (currentProto && currentProto !== Object.prototype) {\n // Use getOwnMetadata to only get metadata directly on this prototype\n const protoKeys: string[] =\n Reflect.getOwnMetadata(PROPERTY_METADATA_KEY, currentProto) || [];\n\n for (const key of protoKeys) {\n if (!seen.has(key)) {\n seen.add(key);\n keys.push(key);\n }\n }\n\n currentProto = Object.getPrototypeOf(currentProto);\n }\n\n return keys;\n }\n\n static getPropertyOptions(\n target: object,\n propertyKey: string,\n ): PropertyOptions | undefined {\n // Determine if we're dealing with a prototype or an instance\n let currentProto: any;\n\n // Check if target is a prototype by checking if it has a constructor property\n // and if target === target.constructor.prototype\n if (target.constructor && target === target.constructor.prototype) {\n // target is already a prototype\n currentProto = target;\n } else {\n // target is an instance, get its prototype\n currentProto = Object.getPrototypeOf(target);\n }\n\n // Walk the prototype chain to find the property options\n while (currentProto && currentProto !== Object.prototype) {\n const protoOptions: Record<string, PropertyOptions> =\n Reflect.getOwnMetadata(PROPERTY_OPTIONS_METADATA_KEY, currentProto) ||\n {};\n\n if (protoOptions[propertyKey]) {\n return protoOptions[propertyKey];\n }\n\n currentProto = Object.getPrototypeOf(currentProto);\n }\n\n return undefined;\n }\n\n static equals(a: unknown, b: unknown): boolean {\n return isEqualWith(a, b, (val1, val2) => {\n if (this.isEntity(val1)) {\n if (!this.sameEntity(val1, val2)) {\n return false;\n }\n\n const diff = this.diff(val1, val2);\n\n return diff.length === 0;\n } else if (\n val1 != null &&\n val2 != null &&\n typeof val1 === 'object' &&\n !Array.isArray(val1) &&\n typeof val2 === 'object' &&\n !Array.isArray(val2) &&\n 'equals' in val1 &&\n typeof val1.equals === 'function'\n ) {\n return val1.equals(val2);\n }\n\n return undefined;\n });\n }\n\n static diff<T extends object>(\n oldEntity: T,\n newEntity: T,\n ): { property: string; oldValue: unknown; newValue: unknown }[] {\n if (!this.sameEntity(oldEntity, newEntity)) {\n throw new Error('Entities must be of the same type to compute diff');\n }\n\n const diffs: { property: string; oldValue: unknown; newValue: unknown }[] =\n [];\n\n const keys = this.getPropertyKeys(oldEntity);\n\n for (const key of keys) {\n const oldValue = (oldEntity as any)[key];\n const newValue = (newEntity as any)[key];\n\n // Check if there's a custom equals function for this property\n const propertyOptions = this.getPropertyOptions(oldEntity, key);\n\n let areEqual: boolean;\n if (oldValue == null && newValue == null) {\n areEqual = oldValue === newValue;\n } else if (oldValue == null || newValue == null) {\n areEqual = false;\n } else {\n areEqual = propertyOptions?.equals\n ? propertyOptions.equals(oldValue, newValue)\n : this.equals(oldValue, newValue);\n }\n\n if (!areEqual) {\n diffs.push({ property: key, oldValue, newValue });\n }\n }\n\n return diffs;\n }\n\n static changes<T extends object>(oldEntity: T, newEntity: T): Partial<T> {\n if (!this.sameEntity(oldEntity, newEntity)) {\n throw new Error('Entities must be of the same type to compute changes');\n }\n\n const diff = this.diff(oldEntity, newEntity);\n\n return diff.reduce((acc, { property, newValue }) => {\n (acc as any)[property] = newValue;\n return acc;\n }, {} as Partial<T>);\n }\n\n /**\n * Serializes an entity to a plain object, converting only properties decorated with @Property()\n *\n * @param entity - The entity instance to serialize\n * @returns A plain object containing only the serialized decorated properties\n *\n * @remarks\n * Serialization rules:\n * - Only properties decorated with @Property() are included\n * - If a property has a custom toJSON() method, it will be used\n * - Nested entities are recursively serialized using EntityUtils.toJSON()\n * - Arrays are mapped with toJSON() applied to each element\n * - Date objects are serialized to ISO strings\n * - bigint values are serialized to strings\n * - undefined values are excluded from the output\n * - null values are included in the output\n * - Circular references are not supported (will cause stack overflow)\n *\n * @example\n * ```typescript\n * @Entity()\n * class Address {\n * @Property() street: string;\n * @Property() city: string;\n * }\n *\n * @Entity()\n * class User {\n * @Property() name: string;\n * @Property() address: Address;\n * @Property() createdAt: Date;\n * undecorated: string; // Will not be serialized\n * }\n *\n * const user = new User();\n * user.name = 'John';\n * user.address = new Address();\n * user.address.street = '123 Main St';\n * user.address.city = 'Boston';\n * user.createdAt = new Date('2024-01-01');\n * user.undecorated = 'ignored';\n *\n * const json = EntityUtils.toJSON(user);\n * // {\n * // name: 'John',\n * // address: { street: '123 Main St', city: 'Boston' },\n * // createdAt: '2024-01-01T00:00:00.000Z'\n * // }\n * ```\n */\n static toJSON<T extends object>(entity: T): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n const keys = this.getPropertyKeys(entity);\n\n for (const key of keys) {\n const value = (entity as any)[key];\n\n // Skip undefined values\n if (value === undefined) {\n continue;\n }\n\n const options = this.getPropertyOptions(entity, key);\n result[key] = this.serializeValue(value, options);\n }\n\n return result;\n }\n\n /**\n * Serializes a single value according to the toJSON rules\n * @private\n */\n private static serializeValue(\n value: unknown,\n options?: PropertyOptions,\n ): unknown {\n if (value === null) {\n return null;\n }\n\n if (value === undefined) {\n return undefined;\n }\n\n const passthrough = options?.passthrough === true;\n if (passthrough) {\n return value;\n }\n\n if (Array.isArray(value)) {\n if (options?.serialize) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return value.map((item) => options.serialize!(item as any));\n }\n return value.map((item) => this.serializeValue(item));\n }\n\n if (options?.serialize) {\n return options.serialize(value as any);\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n if (typeof value === 'bigint') {\n return value.toString();\n }\n\n if (this.isEntity(value)) {\n return this.toJSON(value);\n }\n\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n return value;\n }\n\n throw new Error(\n `Cannot serialize value of type '${typeof value}'. Use passthrough: true in @Property() to explicitly allow serialization of unknown types.`,\n );\n }\n\n /**\n * Deserializes a plain object to an entity instance\n *\n * @param entityClass - The entity class constructor. Must accept a data object parameter.\n * @param plainObject - The plain object to deserialize\n * @param options - Parse options (strict mode)\n * @returns Promise resolving to a new instance of the entity with deserialized values\n *\n * @remarks\n * Deserialization rules:\n * - All @Property() decorators must include type metadata for parse() to work\n * - Properties without type metadata will throw an error\n * - Required properties (optional !== true) must be present and not null/undefined\n * - Optional properties (optional === true) can be undefined or null\n * - Arrays are supported with the array: true option\n * - Nested entities are recursively deserialized\n * - Type conversion is strict (no coercion)\n * - Entity constructors must accept a required data parameter\n *\n * Validation behavior:\n * - If strict: true - both HARD and SOFT problems throw ValidationError\n * - If strict: false (default) - HARD problems throw ValidationError, SOFT problems stored\n * - Property validators run first, then entity validators\n * - Validators can be synchronous or asynchronous\n * - Problems are accessible via EntityUtils.getProblems()\n * - Raw input data is accessible via EntityUtils.getRawInput()\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) name!: string;\n * @Property({ type: () => Number }) age!: number;\n *\n * constructor(data: Partial<User>) {\n * Object.assign(this, data);\n * }\n * }\n *\n * const json = { name: 'John', age: 30 };\n * const user = await EntityUtils.parse(User, json);\n * const userStrict = await EntityUtils.parse(User, json, { strict: true });\n * ```\n */\n static async parse<T extends object>(\n entityClass: new (data: any) => T,\n plainObject: unknown,\n options?: { strict?: boolean },\n ): Promise<T> {\n if (plainObject == null) {\n throw createValidationError(\n `Expects an object but received ${typeof plainObject}`,\n );\n }\n if (Array.isArray(plainObject)) {\n throw createValidationError(`Expects an object but received array`);\n }\n if (typeof plainObject !== 'object') {\n throw createValidationError(\n `Expects an object but received ${typeof plainObject}`,\n );\n }\n\n const strict = options?.strict ?? false;\n const keys = this.getPropertyKeys(entityClass.prototype);\n const data: Record<string, unknown> = {};\n const hardProblems: Problem[] = [];\n\n for (const key of keys) {\n const propertyOptions = this.getPropertyOptions(\n entityClass.prototype,\n key,\n );\n\n if (!propertyOptions) {\n hardProblems.push(\n new Problem({\n property: key,\n message: `Property has no metadata. This should not happen if @Property() was used correctly.`,\n }),\n );\n continue;\n }\n\n const value = (plainObject as Record<string, unknown>)[key];\n\n if (propertyOptions.passthrough === true) {\n data[key] = value;\n continue;\n }\n\n const isOptional = propertyOptions.optional === true;\n\n if (!(key in plainObject)) {\n if (!isOptional) {\n hardProblems.push(\n new Problem({\n property: key,\n message: 'Required property is missing from input',\n }),\n );\n }\n continue;\n }\n\n if (value === null || value === undefined) {\n if (!isOptional) {\n hardProblems.push(\n new Problem({\n property: key,\n message: 'Cannot be null or undefined',\n }),\n );\n }\n data[key] = value;\n continue;\n }\n\n try {\n data[key] = await this.deserializeValue(value, propertyOptions);\n } catch (error) {\n if (error instanceof ValidationError) {\n const problems = prependPropertyPath(key, error);\n hardProblems.push(...problems);\n } else if (error instanceof Error) {\n hardProblems.push(\n new Problem({\n property: key,\n message: error.message,\n }),\n );\n } else {\n throw error;\n }\n }\n }\n\n if (hardProblems.length > 0) {\n throw new ValidationError(hardProblems);\n }\n\n await this.addInjectedDependencies(data, entityClass.prototype);\n\n const instance = new entityClass(data);\n\n rawInputStorage.set(instance, plainObject as Record<string, unknown>);\n\n const problems = await this.validate(instance);\n\n if (problems.length > 0 && strict) {\n throw new ValidationError(problems);\n }\n\n return instance;\n }\n\n /**\n * Safely deserializes a plain object to an entity instance without throwing errors\n *\n * @param entityClass - The entity class constructor. Must accept a data object parameter.\n * @param plainObject - The plain object to deserialize\n * @param options - Parse options (strict mode)\n * @returns Promise resolving to a result object with success flag, data, and problems\n *\n * @remarks\n * Similar to parse() but returns a result object instead of throwing errors:\n * - On success with strict: true - returns { success: true, data, problems: [] }\n * - On success with strict: false - returns { success: true, data, problems: [...] } (may include soft problems)\n * - On failure - returns { success: false, data: undefined, problems: [...] }\n *\n * All deserialization and validation rules from parse() apply.\n * See parse() documentation for detailed deserialization behavior.\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) name!: string;\n * @Property({ type: () => Number }) age!: number;\n *\n * constructor(data: Partial<User>) {\n * Object.assign(this, data);\n * }\n * }\n *\n * const result = await EntityUtils.safeParse(User, { name: 'John', age: 30 });\n * if (result.success) {\n * console.log(result.data); // User instance\n * console.log(result.problems); // [] or soft problems if not strict\n * } else {\n * console.log(result.problems); // Hard problems\n * }\n * ```\n */\n static async safeParse<T extends object>(\n entityClass: new (data: any) => T,\n plainObject: unknown,\n options?: { strict?: boolean },\n ): SafeOperationResult<T> {\n try {\n const data = await this.parse(entityClass, plainObject, options);\n const problems = this.getProblems(data);\n\n return {\n success: true,\n data,\n problems,\n };\n } catch (error) {\n if (error instanceof ValidationError) {\n return {\n success: false,\n data: undefined,\n problems: error.problems,\n };\n }\n throw error;\n }\n }\n\n /**\n * Updates an entity instance with new values, respecting preventUpdates flags on properties\n *\n * @param instance - The entity instance to update. Must be an Entity.\n * @param updates - Partial object with properties to update\n * @param options - Update options (strict mode)\n * @returns Promise resolving to a new instance with updated values\n *\n * @remarks\n * Update behavior:\n * - Creates a shallow copy of the instance\n * - For each @Property(), copies the value from updates if it exists\n * - Properties with preventUpdates: true will not be copied from updates\n * - Runs entity validators after applying updates\n * - Throws ValidationError if validation fails and strict: true\n * - Soft problems are stored on the instance if strict: false (default)\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) name!: string;\n * @Property({ type: () => String, preventUpdates: true }) id!: string;\n *\n * constructor(data: Partial<User>) {\n * Object.assign(this, data);\n * }\n * }\n *\n * const user = new User({ id: '123', name: 'John' });\n * const updated = await EntityUtils.update(user, { id: '456', name: 'Jane' });\n * // updated.id === '123' (not updated due to preventUpdates: true)\n * // updated.name === 'Jane'\n * ```\n */\n static async update<T extends object>(\n instance: T,\n updates: Partial<T>,\n options?: { strict?: boolean },\n ): Promise<T> {\n if (!this.isEntity(instance)) {\n throw new Error('Cannot update non-entity instance');\n }\n\n const strict = options?.strict ?? false;\n const Constructor = Object.getPrototypeOf(instance).constructor;\n const keys = this.getPropertyKeys(instance);\n const data: Record<string, unknown> = {};\n\n // Copy existing properties\n for (const key of keys) {\n const value = (instance as any)[key];\n data[key] = value;\n }\n\n // Apply updates, respecting preventUpdates flag\n for (const key of keys) {\n if (key in updates) {\n const propertyOptions = this.getPropertyOptions(instance, key);\n if (propertyOptions && propertyOptions.preventUpdates === true) {\n // Skip updating this property\n continue;\n }\n data[key] = (updates as any)[key];\n }\n }\n\n const newInstance = new Constructor(data);\n\n const problems = await this.validate(newInstance);\n\n if (problems.length > 0 && strict) {\n throw new ValidationError(problems);\n }\n\n return newInstance;\n }\n\n /**\n * Safely updates an entity instance without throwing errors\n *\n * @param instance - The entity instance to update. Must be an Entity.\n * @param updates - Partial object with properties to update\n * @param options - Update options (strict mode)\n * @returns Promise resolving to a result object with success flag, data, and problems\n *\n * @remarks\n * Similar to update() but returns a result object instead of throwing errors:\n * - On success with strict: true - returns { success: true, data, problems: [] }\n * - On success with strict: false - returns { success: true, data, problems: [...] } (may include soft problems)\n * - On failure - returns { success: false, data: undefined, problems: [...] }\n *\n * All update and validation rules from update() apply.\n * See update() documentation for detailed update behavior.\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) name!: string;\n *\n * constructor(data: Partial<User>) {\n * Object.assign(this, data);\n * }\n * }\n *\n * const user = new User({ name: 'John' });\n * const result = await EntityUtils.safeUpdate(user, { name: 'Jane' });\n * if (result.success) {\n * console.log(result.data); // Updated User instance\n * console.log(result.problems); // [] or soft problems if not strict\n * } else {\n * console.log(result.problems); // Hard problems\n * }\n * ```\n */\n static async safeUpdate<T extends object>(\n instance: T,\n updates: Partial<T>,\n options?: { strict?: boolean },\n ): SafeOperationResult<T> {\n try {\n const updatedInstance = await this.update(instance, updates, options);\n const problems = this.getProblems(updatedInstance);\n\n return {\n success: true,\n data: updatedInstance,\n problems,\n };\n } catch (error) {\n if (error instanceof ValidationError) {\n return {\n success: false,\n data: undefined,\n problems: error.problems,\n };\n }\n throw error;\n }\n }\n\n /**\n * Deserializes a single value according to the type metadata\n * @private\n */\n private static async deserializeValue(\n value: unknown,\n options: PropertyOptions,\n ): Promise<unknown> {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const typeConstructor = options.type!();\n const isArray = options.array === true;\n const isSparse = options.sparse === true;\n\n if (isArray) {\n if (!Array.isArray(value)) {\n throw createValidationError(\n `Expects an array but received ${typeof value}`,\n );\n }\n\n const arrayProblems: Problem[] = [];\n const result: unknown[] = [];\n\n for (let index = 0; index < value.length; index++) {\n const item = value[index];\n if (item === null || item === undefined) {\n if (!isSparse) {\n arrayProblems.push(\n new Problem({\n property: `[${index}]`,\n message: 'Cannot be null or undefined.',\n }),\n );\n }\n result.push(item);\n } else {\n try {\n if (options.deserialize) {\n result.push(options.deserialize(item));\n } else {\n result.push(\n await this.deserializeSingleValue(item, typeConstructor),\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) {\n const problems = prependArrayIndex(index, error);\n arrayProblems.push(...problems);\n } else {\n throw error;\n }\n }\n }\n }\n\n if (arrayProblems.length > 0) {\n throw new ValidationError(arrayProblems);\n }\n\n return result;\n }\n\n if (options.deserialize) {\n return options.deserialize(value);\n }\n\n return await this.deserializeSingleValue(value, typeConstructor);\n }\n\n /**\n * Deserializes a single non-array value\n * Reports validation errors with empty property (caller will prepend context)\n * @private\n */\n private static async deserializeSingleValue(\n value: unknown,\n typeConstructor: any,\n ): Promise<unknown> {\n if (isPrimitiveConstructor(typeConstructor)) {\n return deserializePrimitive(value, typeConstructor);\n }\n\n if (this.isEntity(typeConstructor)) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw createValidationError(\n `Expects an object but received ${typeof value}`,\n );\n }\n\n return await this.parse(\n typeConstructor as new (data: any) => object,\n value as Record<string, unknown>,\n );\n }\n\n throw createValidationError(\n `Has unknown type constructor. Supported types are: String, Number, Boolean, Date, BigInt, and @Entity() classes. Use passthrough: true to explicitly allow unknown types.`,\n );\n }\n\n /**\n * Validates a property value by running validators and nested entity validation.\n * Prepends the property path to all returned problems.\n * @private\n */\n private static async validatePropertyValue(\n propertyPath: string,\n value: unknown,\n validators: PropertyOptions['validators'],\n ): Promise<Problem[]> {\n const problems: Problem[] = [];\n\n if (validators) {\n for (const validator of validators) {\n const validatorProblems = await validator({ value });\n // Prepend propertyPath to all problems\n for (const problem of validatorProblems) {\n problems.push(\n new Problem({\n property: combinePropertyPaths(propertyPath, problem.property),\n message: problem.message,\n }),\n );\n }\n }\n }\n\n if (EntityUtils.isEntity(value)) {\n const existingProblems = problemsStorage.get(value);\n const nestedProblems =\n existingProblems && existingProblems.length > 0\n ? existingProblems\n : await EntityUtils.validate(value);\n\n const prependedProblems = prependPropertyPath(\n propertyPath,\n new ValidationError(nestedProblems),\n );\n problems.push(...prependedProblems);\n }\n\n return problems;\n }\n\n /**\n * Runs property validators for a given property value\n * @private\n */\n private static async runPropertyValidators(\n key: string,\n value: unknown,\n options: PropertyOptions,\n ): Promise<Problem[]> {\n const problems: Problem[] = [];\n const isArray = options?.array === true;\n const isPassthrough = options?.passthrough === true;\n\n if (isPassthrough || !isArray) {\n const valueProblems = await this.validatePropertyValue(\n key,\n value,\n options.validators,\n );\n problems.push(...valueProblems);\n } else {\n ok(Array.isArray(value), 'Value must be an array for array property');\n\n const arrayValidators = options.arrayValidators || [];\n for (const validator of arrayValidators) {\n const validatorProblems = await validator({ value });\n for (const problem of validatorProblems) {\n problems.push(\n new Problem({\n property: combinePropertyPaths(key, problem.property),\n message: problem.message,\n }),\n );\n }\n }\n\n const validators = options.validators || [];\n if (validators.length > 0) {\n for (let i = 0; i < value.length; i++) {\n const element = value[i];\n if (element !== null && element !== undefined) {\n const elementPath = `${key}[${i}]`;\n const elementProblems = await this.validatePropertyValue(\n elementPath,\n element,\n validators,\n );\n problems.push(...elementProblems);\n }\n }\n }\n }\n\n return problems;\n }\n\n private static async addInjectedDependencies(\n data: Record<string, unknown>,\n prototype: object,\n ): Promise<void> {\n const injectedPropertyNames = getInjectedPropertyNames(prototype);\n if (injectedPropertyNames.length === 0) {\n return;\n }\n\n const injectedPropertyOptions = getInjectedPropertyOptions(prototype);\n\n for (const propertyName of injectedPropertyNames) {\n const token = injectedPropertyOptions[propertyName];\n if (token) {\n const dependency = await EntityDI.get(token);\n data[propertyName] = dependency;\n }\n }\n }\n\n /**\n * Validates an entity instance by running all property and entity validators\n *\n * @param instance - The entity instance to validate\n * @returns Promise resolving to array of Problems found during validation (empty if valid)\n *\n * @remarks\n * - Property validators run first, then entity validators\n * - Each validator can be synchronous or asynchronous\n * - Empty array means no problems found\n *\n * @example\n * ```typescript\n * const user = new User({ name: '', age: -5 });\n * const problems = await EntityUtils.validate(user);\n * console.log(problems); // [Problem, Problem, ...]\n * ```\n */\n static async validate<T extends object>(instance: T): Promise<Problem[]> {\n if (!this.isEntity(instance)) {\n throw new Error('Cannot validate non-entity instance');\n }\n\n const problems: Problem[] = [];\n\n const keys = this.getPropertyKeys(instance);\n for (const key of keys) {\n const options = this.getPropertyOptions(instance, key);\n if (options) {\n const value = (instance as any)[key];\n if (value != null) {\n const validationProblems = await this.runPropertyValidators(\n key,\n value,\n options,\n );\n problems.push(...validationProblems);\n }\n }\n }\n\n const entityValidators = this.getEntityValidators(instance);\n for (const validatorMethod of entityValidators) {\n const validatorProblems = await (instance as any)[validatorMethod]();\n if (Array.isArray(validatorProblems)) {\n problems.push(...validatorProblems);\n }\n }\n\n EntityUtils.setProblems(instance, problems);\n\n return problems;\n }\n\n /**\n * Gets the validation problems for an entity instance\n *\n * @param instance - The entity instance\n * @returns Array of Problems (empty if no problems or instance not parsed)\n *\n * @remarks\n * - Only returns problems from the last parse() call\n * - Returns empty array if instance was not created via parse()\n * - Returns empty array if parse() was called with strict: true\n *\n * @example\n * ```typescript\n * const user = EntityUtils.parse(User, data);\n * const problems = EntityUtils.getProblems(user);\n * console.log(problems); // [Problem, ...]\n * ```\n */\n static getProblems<T extends object>(instance: T): Problem[] {\n return problemsStorage.get(instance) || [];\n }\n\n /**\n * Sets the validation problems for an entity instance\n *\n * @param instance - The entity instance\n * @param problems - Array of Problems to associate with the instance\n *\n * @remarks\n * - Overwrites any existing problems for the instance\n * - Pass an empty array to clear problems\n *\n * @example\n * ```typescript\n * const user = new User({ name: 'John' });\n * EntityUtils.setProblems(user, [new Problem({ property: 'name', message: 'Invalid name' })]);\n * ```\n */\n static setProblems<T extends object>(instance: T, problems: Problem[]): void {\n if (problems.length === 0) {\n problemsStorage.delete(instance);\n } else {\n problemsStorage.set(instance, problems);\n }\n }\n\n /**\n * Gets the raw input data that was used to create an entity instance\n *\n * @param instance - The entity instance\n * @returns The raw input object, or undefined if not available\n *\n * @remarks\n * - Only available for instances created via parse()\n * - Returns a reference to the original input data (not a copy)\n *\n * @example\n * ```typescript\n * const user = EntityUtils.parse(User, { name: 'John', age: 30 });\n * const rawInput = EntityUtils.getRawInput(user);\n * console.log(rawInput); // { name: 'John', age: 30 }\n * ```\n */\n static getRawInput<T extends object>(\n instance: T,\n ): Record<string, unknown> | undefined {\n return rawInputStorage.get(instance);\n }\n\n /**\n * Sets the raw input data for an entity instance\n *\n * @param instance - The entity instance\n * @param rawInput - The raw input object to associate with the instance\n *\n * @remarks\n * - Overwrites any existing raw input for the instance\n * - Pass undefined to clear the raw input\n *\n * @example\n * ```typescript\n * const user = new User({ name: 'John' });\n * EntityUtils.setRawInput(user, { name: 'John', age: 30 });\n * ```\n */\n static setRawInput<T extends object>(\n instance: T,\n rawInput: Record<string, unknown> | undefined,\n ): void {\n if (rawInput === undefined) {\n rawInputStorage.delete(instance);\n } else {\n rawInputStorage.set(instance, rawInput);\n }\n }\n\n /**\n * Gets all entity validator method names for an entity\n * @private\n */\n private static getEntityValidators(target: object): string[] {\n let currentProto: any;\n\n if (target.constructor && target === target.constructor.prototype) {\n currentProto = target;\n } else {\n currentProto = Object.getPrototypeOf(target);\n }\n\n const validators: string[] = [];\n const seen = new Set<string>();\n\n while (currentProto && currentProto !== Object.prototype) {\n const protoValidators: string[] =\n Reflect.getOwnMetadata(ENTITY_VALIDATOR_METADATA_KEY, currentProto) ||\n [];\n\n for (const validator of protoValidators) {\n if (!seen.has(validator)) {\n seen.add(validator);\n validators.push(validator);\n }\n }\n\n currentProto = Object.getPrototypeOf(currentProto);\n }\n\n return validators;\n }\n}\n"],"names":["ENTITY_METADATA_KEY","ENTITY_VALIDATOR_METADATA_KEY","PROPERTY_METADATA_KEY","PROPERTY_OPTIONS_METADATA_KEY","getInjectedPropertyNames","getInjectedPropertyOptions","EntityDI","isEqualWith","ValidationError","Problem","prependPropertyPath","prependArrayIndex","createValidationError","combinePropertyPaths","isPrimitiveConstructor","deserializePrimitive","ok","problemsStorage","WeakMap","rawInputStorage","EntityUtils","isEntity","obj","Reflect","hasMetadata","Array","isArray","constructor","Object","getPrototypeOf","sameEntity","a","b","getPropertyKeys","target","currentProto","prototype","keys","seen","Set","protoKeys","getOwnMetadata","key","has","add","push","getPropertyOptions","propertyKey","protoOptions","undefined","equals","val1","val2","diff","length","oldEntity","newEntity","Error","diffs","oldValue","newValue","propertyOptions","areEqual","property","changes","reduce","acc","toJSON","entity","result","value","options","serializeValue","passthrough","serialize","map","item","Date","toISOString","toString","parse","entityClass","plainObject","strict","data","hardProblems","message","isOptional","optional","deserializeValue","error","problems","addInjectedDependencies","instance","set","validate","safeParse","getProblems","success","update","updates","Constructor","preventUpdates","newInstance","safeUpdate","updatedInstance","typeConstructor","type","array","isSparse","sparse","arrayProblems","index","deserialize","deserializeSingleValue","validatePropertyValue","propertyPath","validators","validator","validatorProblems","problem","existingProblems","get","nestedProblems","prependedProblems","runPropertyValidators","isPassthrough","valueProblems","arrayValidators","i","element","elementPath","elementProblems","injectedPropertyNames","injectedPropertyOptions","propertyName","token","dependency","validationProblems","entityValidators","getEntityValidators","validatorMethod","setProblems","delete","getRawInput","setRawInput","rawInput","protoValidators"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/lib/entity-utils.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-function-type */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n ENTITY_METADATA_KEY,\n ENTITY_OPTIONS_METADATA_KEY,\n ENTITY_VALIDATOR_METADATA_KEY,\n ParseOptions,\n PROPERTY_METADATA_KEY,\n PROPERTY_OPTIONS_METADATA_KEY,\n PropertyOptions,\n SafeOperationResult,\n} from './types.js';\nimport type { EntityOptions } from './entity.js';\nimport {\n getInjectedPropertyNames,\n getInjectedPropertyOptions,\n} from './injected-property.js';\nimport { EntityDI } from './entity-di.js';\nimport { isEqualWith } from 'lodash-es';\nimport { ValidationError } from './validation-error.js';\nimport { Problem } from './problem.js';\nimport {\n prependPropertyPath,\n prependArrayIndex,\n createValidationError,\n combinePropertyPaths,\n} from './validation-utils.js';\nimport {\n isPrimitiveConstructor,\n deserializePrimitive,\n} from './primitive-deserializers.js';\nimport { ok } from 'assert';\n\n/**\n * WeakMap to store validation problems for entity instances\n */\nconst problemsStorage = new WeakMap<object, Problem[]>();\n\n/**\n * WeakMap to store raw input data for entity instances\n */\nconst rawInputStorage = new WeakMap<object, unknown>();\n\nexport class EntityUtils {\n /**\n * Checks if a given object is an instance of a class decorated with @Entity()\n * or if the provided value is an entity class itself\n *\n * @param obj - The object or class to check\n * @returns true if the object is an entity instance or entity class, false otherwise\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * name: string;\n * }\n *\n * const user = new User();\n * console.log(EntityUtils.isEntity(user)); // true\n * console.log(EntityUtils.isEntity(User)); // true\n * console.log(EntityUtils.isEntity({})); // false\n * ```\n */\n static isEntity(obj: unknown): obj is object {\n if (obj == null) {\n return false;\n }\n\n // Check if obj is a constructor function (class)\n if (typeof obj === 'function') {\n return Reflect.hasMetadata(ENTITY_METADATA_KEY, obj);\n }\n\n // Check if obj is an object instance\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n return false;\n }\n\n const constructor = Object.getPrototypeOf(obj).constructor;\n return Reflect.hasMetadata(ENTITY_METADATA_KEY, constructor);\n }\n\n /**\n * Gets the entity options for a given constructor\n *\n * @param entityOrClass - The entity class constructor or instance\n * @returns EntityOptions object (empty object if no options are defined)\n * @private\n */\n private static getEntityOptions(entityOrClass: unknown): EntityOptions {\n const constructor =\n typeof entityOrClass === 'function'\n ? entityOrClass\n : Object.getPrototypeOf(entityOrClass).constructor;\n\n const options: EntityOptions | undefined = Reflect.getMetadata(\n ENTITY_OPTIONS_METADATA_KEY,\n constructor,\n );\n return options ?? {};\n }\n\n /**\n * Checks if a given entity is marked as a collection entity\n *\n * @param entityOrClass - The entity instance or class to check\n * @returns true if the entity is a collection entity, false otherwise\n *\n * @example\n * ```typescript\n * @CollectionEntity()\n * class Tags {\n * @ArrayProperty(() => String)\n * collection: string[];\n * }\n *\n * const tags = new Tags({ collection: ['a', 'b'] });\n * console.log(EntityUtils.isCollectionEntity(tags)); // true\n * console.log(EntityUtils.isCollectionEntity(Tags)); // true\n * ```\n */\n static isCollectionEntity(entityOrClass: unknown): boolean {\n if (!this.isEntity(entityOrClass)) {\n return false;\n }\n\n const options = this.getEntityOptions(entityOrClass);\n\n return options.collection === true;\n }\n\n static sameEntity(a: object, b: object): boolean {\n if (!this.isEntity(a) || !this.isEntity(b)) {\n return false;\n }\n\n return Object.getPrototypeOf(a) === Object.getPrototypeOf(b);\n }\n\n static getPropertyKeys(target: object): string[] {\n // Determine if we're dealing with a prototype or an instance\n let currentProto: any;\n\n // Check if target is a prototype by checking if it has a constructor property\n // and if target === target.constructor.prototype\n if (target.constructor && target === target.constructor.prototype) {\n // target is already a prototype\n currentProto = target;\n } else {\n // target is an instance, get its prototype\n currentProto = Object.getPrototypeOf(target);\n }\n\n const keys: string[] = [];\n const seen = new Set<string>();\n\n // Walk the prototype chain to collect all inherited properties\n while (currentProto && currentProto !== Object.prototype) {\n // Use getOwnMetadata to only get metadata directly on this prototype\n const protoKeys: string[] =\n Reflect.getOwnMetadata(PROPERTY_METADATA_KEY, currentProto) || [];\n\n for (const key of protoKeys) {\n if (!seen.has(key)) {\n seen.add(key);\n keys.push(key);\n }\n }\n\n currentProto = Object.getPrototypeOf(currentProto);\n }\n\n return keys;\n }\n\n static getPropertyOptions(\n target: object,\n propertyKey: string,\n ): PropertyOptions | undefined {\n // Determine if we're dealing with a prototype or an instance\n let currentProto: any;\n\n // Check if target is a prototype by checking if it has a constructor property\n // and if target === target.constructor.prototype\n if (target.constructor && target === target.constructor.prototype) {\n // target is already a prototype\n currentProto = target;\n } else {\n // target is an instance, get its prototype\n currentProto = Object.getPrototypeOf(target);\n }\n\n // Walk the prototype chain to find the property options\n while (currentProto && currentProto !== Object.prototype) {\n const protoOptions: Record<string, PropertyOptions> =\n Reflect.getOwnMetadata(PROPERTY_OPTIONS_METADATA_KEY, currentProto) ||\n {};\n\n if (protoOptions[propertyKey]) {\n return protoOptions[propertyKey];\n }\n\n currentProto = Object.getPrototypeOf(currentProto);\n }\n\n return undefined;\n }\n\n static equals(a: unknown, b: unknown): boolean {\n return isEqualWith(a, b, (val1, val2) => {\n if (this.isEntity(val1)) {\n if (!this.sameEntity(val1, val2)) {\n return false;\n }\n\n const diff = this.diff(val1, val2);\n\n return diff.length === 0;\n } else if (\n val1 != null &&\n val2 != null &&\n typeof val1 === 'object' &&\n !Array.isArray(val1) &&\n typeof val2 === 'object' &&\n !Array.isArray(val2) &&\n 'equals' in val1 &&\n typeof val1.equals === 'function'\n ) {\n return val1.equals(val2);\n }\n\n return undefined;\n });\n }\n\n static diff<T extends object>(\n oldEntity: T,\n newEntity: T,\n ): { property: string; oldValue: unknown; newValue: unknown }[] {\n if (!this.sameEntity(oldEntity, newEntity)) {\n throw new Error('Entities must be of the same type to compute diff');\n }\n\n const diffs: { property: string; oldValue: unknown; newValue: unknown }[] =\n [];\n\n const keys = this.getPropertyKeys(oldEntity);\n\n for (const key of keys) {\n const oldValue = (oldEntity as any)[key];\n const newValue = (newEntity as any)[key];\n\n // Check if there's a custom equals function for this property\n const propertyOptions = this.getPropertyOptions(oldEntity, key);\n\n let areEqual: boolean;\n if (oldValue == null && newValue == null) {\n areEqual = oldValue === newValue;\n } else if (oldValue == null || newValue == null) {\n areEqual = false;\n } else {\n areEqual = propertyOptions?.equals\n ? propertyOptions.equals(oldValue, newValue)\n : this.equals(oldValue, newValue);\n }\n\n if (!areEqual) {\n diffs.push({ property: key, oldValue, newValue });\n }\n }\n\n return diffs;\n }\n\n static changes<T extends object>(oldEntity: T, newEntity: T): Partial<T> {\n if (!this.sameEntity(oldEntity, newEntity)) {\n throw new Error('Entities must be of the same type to compute changes');\n }\n\n const diff = this.diff(oldEntity, newEntity);\n\n return diff.reduce((acc, { property, newValue }) => {\n (acc as any)[property] = newValue;\n return acc;\n }, {} as Partial<T>);\n }\n\n /**\n * Serializes an entity to a plain object, converting only properties decorated with @Property()\n *\n * @param entity - The entity instance to serialize\n * @returns A plain object containing only the serialized decorated properties, or an array for collection entities\n *\n * @remarks\n * Serialization rules:\n * - Only properties decorated with @Property() are included\n * - If a property has a custom toJSON() method, it will be used\n * - Nested entities are recursively serialized using EntityUtils.toJSON()\n * - Arrays are mapped with toJSON() applied to each element\n * - Date objects are serialized to ISO strings\n * - bigint values are serialized to strings\n * - undefined values are excluded from the output\n * - null values are included in the output\n * - Circular references are not supported (will cause stack overflow)\n * - Collection entities (@CollectionEntity) are unwrapped to just their array\n *\n * @example\n * ```typescript\n * @Entity()\n * class Address {\n * @Property() street: string;\n * @Property() city: string;\n * }\n *\n * @Entity()\n * class User {\n * @Property() name: string;\n * @Property() address: Address;\n * @Property() createdAt: Date;\n * undecorated: string; // Will not be serialized\n * }\n *\n * const user = new User();\n * user.name = 'John';\n * user.address = new Address();\n * user.address.street = '123 Main St';\n * user.address.city = 'Boston';\n * user.createdAt = new Date('2024-01-01');\n * user.undecorated = 'ignored';\n *\n * const json = EntityUtils.toJSON(user);\n * // {\n * // name: 'John',\n * // address: { street: '123 Main St', city: 'Boston' },\n * // createdAt: '2024-01-01T00:00:00.000Z'\n * // }\n *\n * @CollectionEntity()\n * class Tags {\n * @ArrayProperty(() => String)\n * collection: string[];\n * }\n *\n * const tags = new Tags({ collection: ['a', 'b'] });\n * const json = EntityUtils.toJSON(tags);\n * // ['a', 'b'] - unwrapped to array\n * ```\n */\n static toJSON<T extends object>(entity: T): unknown {\n if (this.isCollectionEntity(entity)) {\n const collectionPropertyOptions = this.getPropertyOptions(\n entity,\n 'collection',\n );\n if (!collectionPropertyOptions) {\n throw new Error(\n `Collection entity 'collection' property is missing metadata`,\n );\n }\n if (!collectionPropertyOptions.array) {\n throw new Error(\n `Collection entity 'collection' property must be an array`,\n );\n }\n\n return this.serializeValue(\n (entity as any).collection,\n collectionPropertyOptions,\n );\n }\n\n const result: Record<string, unknown> = {};\n const keys = this.getPropertyKeys(entity);\n\n for (const key of keys) {\n const value = (entity as any)[key];\n\n // Skip undefined values\n if (value === undefined) {\n continue;\n }\n\n const options = this.getPropertyOptions(entity, key);\n result[key] = this.serializeValue(value, options);\n }\n\n return result;\n }\n\n /**\n * Serializes a single value according to the toJSON rules\n * @private\n */\n private static serializeValue(\n value: unknown,\n options?: PropertyOptions,\n ): unknown {\n if (value === null) {\n return null;\n }\n\n if (value === undefined) {\n return undefined;\n }\n\n const passthrough = options?.passthrough === true;\n if (passthrough) {\n return value;\n }\n\n if (Array.isArray(value)) {\n if (options?.serialize) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return value.map((item) => options.serialize!(item as any));\n }\n return value.map((item) => this.serializeValue(item));\n }\n\n if (options?.serialize) {\n return options.serialize(value as any);\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n if (typeof value === 'bigint') {\n return value.toString();\n }\n\n if (this.isEntity(value)) {\n return this.toJSON(value);\n }\n\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n return value;\n }\n\n throw new Error(\n `Cannot serialize value of type '${typeof value}'. Use passthrough: true in @Property() to explicitly allow serialization of unknown types.`,\n );\n }\n\n /**\n * Deserializes a plain object to an entity instance\n *\n * @param entityClass - The entity class constructor. Must accept a data object parameter.\n * @param plainObject - The plain object to deserialize\n * @param parseOptions - Parse options (strict mode)\n * @returns Promise resolving to a new instance of the entity with deserialized values\n *\n * @remarks\n * Deserialization rules:\n * - All @Property() decorators must include type metadata for parse() to work\n * - Properties without type metadata will throw an error\n * - Required properties (optional !== true) must be present and not null/undefined\n * - Optional properties (optional === true) can be undefined or null\n * - Arrays are supported with the array: true option\n * - Nested entities are recursively deserialized\n * - Type conversion is strict (no coercion)\n * - Entity constructors must accept a required data parameter\n *\n * Validation behavior:\n * - If strict: true - both HARD and SOFT problems throw ValidationError\n * - If strict: false (default) - HARD problems throw ValidationError, SOFT problems stored\n * - Property validators run first, then entity validators\n * - Validators can be synchronous or asynchronous\n * - Problems are accessible via EntityUtils.getProblems()\n * - Raw input data is accessible via EntityUtils.getRawInput()\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) name!: string;\n * @Property({ type: () => Number }) age!: number;\n *\n * constructor(data: Partial<User>) {\n * Object.assign(this, data);\n * }\n * }\n *\n * const json = { name: 'John', age: 30 };\n * const user = await EntityUtils.parse(User, json);\n * const userStrict = await EntityUtils.parse(User, json, { strict: true });\n * ```\n */\n static async parse<T extends object>(\n entityClass: new (data: any) => T,\n plainObject: unknown,\n parseOptions: ParseOptions = {},\n ): Promise<T> {\n if (this.isCollectionEntity(entityClass)) {\n plainObject = { collection: plainObject };\n }\n if (plainObject == null) {\n throw createValidationError(\n `Expects an object but received ${typeof plainObject}`,\n );\n }\n if (Array.isArray(plainObject)) {\n throw createValidationError(`Expects an object but received array`);\n }\n if (typeof plainObject !== 'object') {\n throw createValidationError(\n `Expects an object but received ${typeof plainObject}`,\n );\n }\n\n const strict = parseOptions?.strict ?? false;\n const keys = this.getPropertyKeys(entityClass.prototype);\n const data: Record<string, unknown> = {};\n const hardProblems: Problem[] = [];\n\n for (const key of keys) {\n const propertyOptions = this.getPropertyOptions(\n entityClass.prototype,\n key,\n );\n\n if (!propertyOptions) {\n hardProblems.push(\n new Problem({\n property: key,\n message: `Property has no metadata. This should not happen if @Property() was used correctly.`,\n }),\n );\n continue;\n }\n\n const value = (plainObject as Record<string, unknown>)[key];\n\n if (propertyOptions.passthrough === true) {\n data[key] = value;\n continue;\n }\n\n const isOptional = propertyOptions.optional === true;\n\n if (!(key in plainObject) || value == null) {\n let valueToSet = value;\n\n if (propertyOptions.default !== undefined) {\n valueToSet =\n typeof propertyOptions.default === 'function'\n ? await propertyOptions.default()\n : propertyOptions.default;\n }\n\n if (!isOptional && valueToSet == null) {\n hardProblems.push(\n new Problem({\n property: key,\n message:\n 'Required property is missing, null or undefined from input',\n }),\n );\n }\n data[key] = valueToSet;\n continue;\n }\n\n try {\n data[key] = await this.deserializeValue(\n value,\n propertyOptions,\n parseOptions,\n );\n } catch (error) {\n if (error instanceof ValidationError) {\n const problems = prependPropertyPath(key, error);\n hardProblems.push(...problems);\n } else if (error instanceof Error) {\n hardProblems.push(\n new Problem({\n property: key,\n message: error.message,\n }),\n );\n } else {\n throw error;\n }\n }\n }\n\n if (hardProblems.length > 0) {\n throw new ValidationError(hardProblems);\n }\n\n await this.addInjectedDependencies(data, entityClass.prototype);\n\n const instance = new entityClass(data);\n\n rawInputStorage.set(instance, plainObject as Record<string, unknown>);\n\n const problems = await this.validate(instance);\n\n if (problems.length > 0 && strict) {\n throw new ValidationError(problems);\n }\n\n return instance;\n }\n\n /**\n * Safely deserializes a plain object to an entity instance without throwing errors\n *\n * @param entityClass - The entity class constructor. Must accept a data object parameter.\n * @param plainObject - The plain object to deserialize\n * @param parseOptions - Parse options (strict mode)\n * @returns Promise resolving to a result object with success flag, data, and problems\n *\n * @remarks\n * Similar to parse() but returns a result object instead of throwing errors:\n * - On success with strict: true - returns { success: true, data, problems: [] }\n * - On success with strict: false - returns { success: true, data, problems: [...] } (may include soft problems)\n * - On failure - returns { success: false, data: undefined, problems: [...] }\n *\n * All deserialization and validation rules from parse() apply.\n * See parse() documentation for detailed deserialization behavior.\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) name!: string;\n * @Property({ type: () => Number }) age!: number;\n *\n * constructor(data: Partial<User>) {\n * Object.assign(this, data);\n * }\n * }\n *\n * const result = await EntityUtils.safeParse(User, { name: 'John', age: 30 });\n * if (result.success) {\n * console.log(result.data); // User instance\n * console.log(result.problems); // [] or soft problems if not strict\n * } else {\n * console.log(result.problems); // Hard problems\n * }\n * ```\n */\n static async safeParse<T extends object>(\n entityClass: new (data: any) => T,\n plainObject: unknown,\n parseOptions?: ParseOptions,\n ): SafeOperationResult<T> {\n try {\n const data = await this.parse(entityClass, plainObject, parseOptions);\n const problems = this.getProblems(data);\n\n return {\n success: true,\n data,\n problems,\n };\n } catch (error) {\n if (error instanceof ValidationError) {\n return {\n success: false,\n data: undefined,\n problems: error.problems,\n };\n }\n throw error;\n }\n }\n\n /**\n * Updates an entity instance with new values, respecting preventUpdates flags on properties\n *\n * @param instance - The entity instance to update. Must be an Entity.\n * @param updates - Partial object with properties to update\n * @param options - Update options (strict mode)\n * @returns Promise resolving to a new instance with updated values\n *\n * @remarks\n * Update behavior:\n * - Creates a shallow copy of the instance\n * - For each @Property(), copies the value from updates if it exists\n * - Properties with preventUpdates: true will not be copied from updates\n * - Runs entity validators after applying updates\n * - Throws ValidationError if validation fails and strict: true\n * - Soft problems are stored on the instance if strict: false (default)\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) name!: string;\n * @Property({ type: () => String, preventUpdates: true }) id!: string;\n *\n * constructor(data: Partial<User>) {\n * Object.assign(this, data);\n * }\n * }\n *\n * const user = new User({ id: '123', name: 'John' });\n * const updated = await EntityUtils.update(user, { id: '456', name: 'Jane' });\n * // updated.id === '123' (not updated due to preventUpdates: true)\n * // updated.name === 'Jane'\n * ```\n */\n static async update<T extends object>(\n instance: T,\n updates: Partial<T>,\n options?: { strict?: boolean },\n ): Promise<T> {\n if (!this.isEntity(instance)) {\n throw new Error('Cannot update non-entity instance');\n }\n\n const strict = options?.strict ?? false;\n const Constructor = Object.getPrototypeOf(instance).constructor;\n const keys = this.getPropertyKeys(instance);\n const data: Record<string, unknown> = {};\n\n // Copy existing properties\n for (const key of keys) {\n const value = (instance as any)[key];\n data[key] = value;\n }\n\n // Apply updates, respecting preventUpdates flag\n for (const key of keys) {\n if (key in updates) {\n const propertyOptions = this.getPropertyOptions(instance, key);\n if (propertyOptions && propertyOptions.preventUpdates === true) {\n // Skip updating this property\n continue;\n }\n data[key] = (updates as any)[key];\n }\n }\n\n const newInstance = new Constructor(data);\n\n const problems = await this.validate(newInstance);\n\n if (problems.length > 0 && strict) {\n throw new ValidationError(problems);\n }\n\n return newInstance;\n }\n\n /**\n * Safely updates an entity instance without throwing errors\n *\n * @param instance - The entity instance to update. Must be an Entity.\n * @param updates - Partial object with properties to update\n * @param options - Update options (strict mode)\n * @returns Promise resolving to a result object with success flag, data, and problems\n *\n * @remarks\n * Similar to update() but returns a result object instead of throwing errors:\n * - On success with strict: true - returns { success: true, data, problems: [] }\n * - On success with strict: false - returns { success: true, data, problems: [...] } (may include soft problems)\n * - On failure - returns { success: false, data: undefined, problems: [...] }\n *\n * All update and validation rules from update() apply.\n * See update() documentation for detailed update behavior.\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) name!: string;\n *\n * constructor(data: Partial<User>) {\n * Object.assign(this, data);\n * }\n * }\n *\n * const user = new User({ name: 'John' });\n * const result = await EntityUtils.safeUpdate(user, { name: 'Jane' });\n * if (result.success) {\n * console.log(result.data); // Updated User instance\n * console.log(result.problems); // [] or soft problems if not strict\n * } else {\n * console.log(result.problems); // Hard problems\n * }\n * ```\n */\n static async safeUpdate<T extends object>(\n instance: T,\n updates: Partial<T>,\n options?: { strict?: boolean },\n ): SafeOperationResult<T> {\n try {\n const updatedInstance = await this.update(instance, updates, options);\n const problems = this.getProblems(updatedInstance);\n\n return {\n success: true,\n data: updatedInstance,\n problems,\n };\n } catch (error) {\n if (error instanceof ValidationError) {\n return {\n success: false,\n data: undefined,\n problems: error.problems,\n };\n }\n throw error;\n }\n }\n\n /**\n * Deserializes a single value according to the type metadata\n * @private\n */\n private static async deserializeValue(\n value: unknown,\n options: PropertyOptions,\n parseOptions: ParseOptions,\n ): Promise<unknown> {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const typeConstructor = options.type!();\n const isArray = options.array === true;\n const isSparse = options.sparse === true;\n\n if (isArray) {\n if (!Array.isArray(value)) {\n throw createValidationError(\n `Expects an array but received ${typeof value}`,\n );\n }\n\n const arrayProblems: Problem[] = [];\n const result: unknown[] = [];\n\n for (let index = 0; index < value.length; index++) {\n const item = value[index];\n if (item === null || item === undefined) {\n if (!isSparse) {\n arrayProblems.push(\n new Problem({\n property: `[${index}]`,\n message: 'Cannot be null or undefined.',\n }),\n );\n }\n result.push(item);\n } else {\n try {\n if (options.deserialize) {\n result.push(options.deserialize(item));\n } else {\n result.push(\n await this.deserializeSingleValue(\n item,\n typeConstructor,\n parseOptions,\n ),\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) {\n const problems = prependArrayIndex(index, error);\n arrayProblems.push(...problems);\n } else {\n throw error;\n }\n }\n }\n }\n\n if (arrayProblems.length > 0) {\n throw new ValidationError(arrayProblems);\n }\n\n return result;\n }\n\n if (options.deserialize) {\n return options.deserialize(value);\n }\n\n return await this.deserializeSingleValue(\n value,\n typeConstructor,\n parseOptions,\n );\n }\n\n /**\n * Deserializes a single non-array value\n * Reports validation errors with empty property (caller will prepend context)\n * @private\n */\n private static async deserializeSingleValue(\n value: unknown,\n typeConstructor: any,\n parseOptions: ParseOptions,\n ): Promise<unknown> {\n if (isPrimitiveConstructor(typeConstructor)) {\n return deserializePrimitive(value, typeConstructor);\n }\n\n if (this.isEntity(typeConstructor)) {\n return await this.parse(\n typeConstructor as new (data: any) => object,\n value as Record<string, unknown>,\n parseOptions,\n );\n }\n\n throw createValidationError(\n `Has unknown type constructor. Supported types are: String, Number, Boolean, Date, BigInt, and @Entity() classes. Use passthrough: true to explicitly allow unknown types.`,\n );\n }\n\n /**\n * Validates a property value by running validators and nested entity validation.\n * Prepends the property path to all returned problems.\n * @private\n */\n private static async validatePropertyValue(\n propertyPath: string,\n value: unknown,\n validators: PropertyOptions['validators'],\n ): Promise<Problem[]> {\n const problems: Problem[] = [];\n\n if (validators) {\n for (const validator of validators) {\n const validatorProblems = await validator({ value });\n // Prepend propertyPath to all problems\n for (const problem of validatorProblems) {\n problems.push(\n new Problem({\n property: combinePropertyPaths(propertyPath, problem.property),\n message: problem.message,\n }),\n );\n }\n }\n }\n\n if (EntityUtils.isEntity(value)) {\n const existingProblems = problemsStorage.get(value);\n const nestedProblems =\n existingProblems && existingProblems.length > 0\n ? existingProblems\n : await EntityUtils.validate(value);\n\n const prependedProblems = prependPropertyPath(\n propertyPath,\n new ValidationError(nestedProblems),\n );\n problems.push(...prependedProblems);\n }\n\n return problems;\n }\n\n /**\n * Runs property validators for a given property value\n * @private\n */\n private static async runPropertyValidators(\n key: string,\n value: unknown,\n options: PropertyOptions,\n ): Promise<Problem[]> {\n const problems: Problem[] = [];\n const isArray = options?.array === true;\n const isPassthrough = options?.passthrough === true;\n\n if (isPassthrough || !isArray) {\n const valueProblems = await this.validatePropertyValue(\n key,\n value,\n options.validators,\n );\n problems.push(...valueProblems);\n } else {\n ok(Array.isArray(value), 'Value must be an array for array property');\n\n const arrayValidators = options.arrayValidators || [];\n for (const validator of arrayValidators) {\n const validatorProblems = await validator({ value });\n for (const problem of validatorProblems) {\n problems.push(\n new Problem({\n property: combinePropertyPaths(key, problem.property),\n message: problem.message,\n }),\n );\n }\n }\n\n const validators = options.validators || [];\n if (validators.length > 0) {\n for (let i = 0; i < value.length; i++) {\n const element = value[i];\n if (element !== null && element !== undefined) {\n const elementPath = `${key}[${i}]`;\n const elementProblems = await this.validatePropertyValue(\n elementPath,\n element,\n validators,\n );\n problems.push(...elementProblems);\n }\n }\n }\n }\n\n return problems;\n }\n\n private static async addInjectedDependencies(\n data: Record<string, unknown>,\n prototype: object,\n ): Promise<void> {\n const injectedPropertyNames = getInjectedPropertyNames(prototype);\n if (injectedPropertyNames.length === 0) {\n return;\n }\n\n const injectedPropertyOptions = getInjectedPropertyOptions(prototype);\n\n for (const propertyName of injectedPropertyNames) {\n const token = injectedPropertyOptions[propertyName];\n if (token) {\n const dependency = await EntityDI.get(token);\n data[propertyName] = dependency;\n }\n }\n }\n\n /**\n * Validates an entity instance by running all property and entity validators\n *\n * @param instance - The entity instance to validate\n * @returns Promise resolving to array of Problems found during validation (empty if valid)\n *\n * @remarks\n * - Property validators run first, then entity validators\n * - Each validator can be synchronous or asynchronous\n * - Empty array means no problems found\n *\n * @example\n * ```typescript\n * const user = new User({ name: '', age: -5 });\n * const problems = await EntityUtils.validate(user);\n * console.log(problems); // [Problem, Problem, ...]\n * ```\n */\n static async validate<T extends object>(instance: T): Promise<Problem[]> {\n if (!this.isEntity(instance)) {\n throw new Error('Cannot validate non-entity instance');\n }\n\n const problems: Problem[] = [];\n\n const keys = this.getPropertyKeys(instance);\n for (const key of keys) {\n const options = this.getPropertyOptions(instance, key);\n if (options) {\n const value = (instance as any)[key];\n if (value != null) {\n const validationProblems = await this.runPropertyValidators(\n key,\n value,\n options,\n );\n problems.push(...validationProblems);\n }\n }\n }\n\n const entityValidators = this.getEntityValidators(instance);\n for (const validatorMethod of entityValidators) {\n const validatorProblems = await (instance as any)[validatorMethod]();\n if (Array.isArray(validatorProblems)) {\n problems.push(...validatorProblems);\n }\n }\n\n EntityUtils.setProblems(instance, problems);\n\n return problems;\n }\n\n /**\n * Gets the validation problems for an entity instance\n *\n * @param instance - The entity instance\n * @returns Array of Problems (empty if no problems or instance not parsed)\n *\n * @remarks\n * - Only returns problems from the last parse() call\n * - Returns empty array if instance was not created via parse()\n * - Returns empty array if parse() was called with strict: true\n *\n * @example\n * ```typescript\n * const user = EntityUtils.parse(User, data);\n * const problems = EntityUtils.getProblems(user);\n * console.log(problems); // [Problem, ...]\n * ```\n */\n static getProblems<T extends object>(instance: T): Problem[] {\n return problemsStorage.get(instance) || [];\n }\n\n /**\n * Sets the validation problems for an entity instance\n *\n * @param instance - The entity instance\n * @param problems - Array of Problems to associate with the instance\n *\n * @remarks\n * - Overwrites any existing problems for the instance\n * - Pass an empty array to clear problems\n *\n * @example\n * ```typescript\n * const user = new User({ name: 'John' });\n * EntityUtils.setProblems(user, [new Problem({ property: 'name', message: 'Invalid name' })]);\n * ```\n */\n static setProblems<T extends object>(instance: T, problems: Problem[]): void {\n if (problems.length === 0) {\n problemsStorage.delete(instance);\n } else {\n problemsStorage.set(instance, problems);\n }\n }\n\n /**\n * Gets the raw input data that was used to create an entity instance\n *\n * @param instance - The entity instance\n * @returns The raw input object, or undefined if not available\n *\n * @remarks\n * - Only available for instances created via parse()\n * - Returns a reference to the original input data (not a copy)\n *\n * @example\n * ```typescript\n * const user = EntityUtils.parse(User, { name: 'John', age: 30 });\n * const rawInput = EntityUtils.getRawInput(user);\n * console.log(rawInput); // { name: 'John', age: 30 }\n * ```\n */\n static getRawInput<T extends object>(instance: T): unknown {\n return rawInputStorage.get(instance);\n }\n\n /**\n * Sets the raw input data for an entity instance\n *\n * @param instance - The entity instance\n * @param rawInput - The raw input object to associate with the instance\n *\n * @remarks\n * - Overwrites any existing raw input for the instance\n * - Pass undefined to clear the raw input\n *\n * @example\n * ```typescript\n * const user = new User({ name: 'John' });\n * EntityUtils.setRawInput(user, { name: 'John', age: 30 });\n * ```\n */\n static setRawInput<T extends object>(\n instance: T,\n rawInput: Record<string, unknown> | undefined,\n ): void {\n if (rawInput === undefined) {\n rawInputStorage.delete(instance);\n } else {\n rawInputStorage.set(instance, rawInput);\n }\n }\n\n /**\n * Gets all entity validator method names for an entity\n * @private\n */\n private static getEntityValidators(target: object): string[] {\n let currentProto: any;\n\n if (target.constructor && target === target.constructor.prototype) {\n currentProto = target;\n } else {\n currentProto = Object.getPrototypeOf(target);\n }\n\n const validators: string[] = [];\n const seen = new Set<string>();\n\n while (currentProto && currentProto !== Object.prototype) {\n const protoValidators: string[] =\n Reflect.getOwnMetadata(ENTITY_VALIDATOR_METADATA_KEY, currentProto) ||\n [];\n\n for (const validator of protoValidators) {\n if (!seen.has(validator)) {\n seen.add(validator);\n validators.push(validator);\n }\n }\n\n currentProto = Object.getPrototypeOf(currentProto);\n }\n\n return validators;\n }\n}\n"],"names":["ENTITY_METADATA_KEY","ENTITY_OPTIONS_METADATA_KEY","ENTITY_VALIDATOR_METADATA_KEY","PROPERTY_METADATA_KEY","PROPERTY_OPTIONS_METADATA_KEY","getInjectedPropertyNames","getInjectedPropertyOptions","EntityDI","isEqualWith","ValidationError","Problem","prependPropertyPath","prependArrayIndex","createValidationError","combinePropertyPaths","isPrimitiveConstructor","deserializePrimitive","ok","problemsStorage","WeakMap","rawInputStorage","EntityUtils","isEntity","obj","Reflect","hasMetadata","Array","isArray","constructor","Object","getPrototypeOf","getEntityOptions","entityOrClass","options","getMetadata","isCollectionEntity","collection","sameEntity","a","b","getPropertyKeys","target","currentProto","prototype","keys","seen","Set","protoKeys","getOwnMetadata","key","has","add","push","getPropertyOptions","propertyKey","protoOptions","undefined","equals","val1","val2","diff","length","oldEntity","newEntity","Error","diffs","oldValue","newValue","propertyOptions","areEqual","property","changes","reduce","acc","toJSON","entity","collectionPropertyOptions","array","serializeValue","result","value","passthrough","serialize","map","item","Date","toISOString","toString","parse","entityClass","plainObject","parseOptions","strict","data","hardProblems","message","isOptional","optional","valueToSet","default","deserializeValue","error","problems","addInjectedDependencies","instance","set","validate","safeParse","getProblems","success","update","updates","Constructor","preventUpdates","newInstance","safeUpdate","updatedInstance","typeConstructor","type","isSparse","sparse","arrayProblems","index","deserialize","deserializeSingleValue","validatePropertyValue","propertyPath","validators","validator","validatorProblems","problem","existingProblems","get","nestedProblems","prependedProblems","runPropertyValidators","isPassthrough","valueProblems","arrayValidators","i","element","elementPath","elementProblems","injectedPropertyNames","injectedPropertyOptions","propertyName","token","dependency","validationProblems","entityValidators","getEntityValidators","validatorMethod","setProblems","delete","getRawInput","setRawInput","rawInput","protoValidators"],"mappings":"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"}
@@ -1,16 +1,72 @@
1
+ /**
2
+ * Options for Entity decorator
3
+ */
4
+ export interface EntityOptions {
5
+ /**
6
+ * Whether this entity represents a collection.
7
+ * Collection entities must have a 'collection' property that is an array.
8
+ * When serialized, collection entities are unwrapped to just their array.
9
+ * When deserialized from an array, the array is wrapped in { collection: [...] }.
10
+ */
11
+ collection?: boolean;
12
+ }
1
13
  /**
2
14
  * Decorator that marks a class as an Entity.
3
15
  * This allows us to identify entity instances later.
4
16
  *
17
+ * @param options - Optional configuration for the entity
18
+ *
5
19
  * @example
6
20
  * ```typescript
7
21
  * @Entity()
8
22
  * class User {
9
23
  * name: string;
10
24
  * }
25
+ *
26
+ * @Entity({ collection: true })
27
+ * class Tags {
28
+ * @ArrayProperty(() => String)
29
+ * collection: string[];
30
+ * }
31
+ * ```
32
+ */
33
+ export declare function Entity(options?: EntityOptions): ClassDecorator;
34
+ /**
35
+ * Decorator that marks a class as a Collection Entity.
36
+ * This is syntax sugar for @Entity({ collection: true }).
37
+ *
38
+ * Collection entities must have a 'collection' property that is an array.
39
+ * When serialized with EntityUtils.toJSON(), they are unwrapped to just the array.
40
+ * When deserialized from an array, the array is wrapped in { collection: [...] }.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * @CollectionEntity()
45
+ * class Tags {
46
+ * @ArrayProperty(() => String)
47
+ * readonly collection: string[];
48
+ *
49
+ * constructor(data: { collection: string[] }) {
50
+ * this.collection = data.collection;
51
+ * }
52
+ * }
53
+ *
54
+ * @Entity()
55
+ * class Article {
56
+ * @EntityProperty(() => Tags)
57
+ * tags!: Tags;
58
+ * }
59
+ *
60
+ * const article = new Article(...);
61
+ * const json = EntityUtils.toJSON(article);
62
+ * // { tags: ["tag1", "tag2"] } - unwrapped to array
63
+ *
64
+ * // Also works when serializing the collection directly:
65
+ * const tagsJson = EntityUtils.toJSON(tags);
66
+ * // ["tag1", "tag2"] - unwrapped to array
11
67
  * ```
12
68
  */
13
- export declare function Entity(): ClassDecorator;
69
+ export declare function CollectionEntity(): ClassDecorator;
14
70
  /**
15
71
  * Decorator that marks a method as an entity validator.
16
72
  * The method should return an array of Problems.
@@ -1 +1 @@
1
- {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../src/lib/entity.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,IAAI,cAAc,CAMvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,IAAI,eAAe,CAmBjD"}
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../src/lib/entity.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,cAAc,CASlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,gBAAgB,IAAI,cAAc,CAEjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,IAAI,eAAe,CAmBjD"}
@@ -1,22 +1,72 @@
1
- import { ENTITY_METADATA_KEY, ENTITY_VALIDATOR_METADATA_KEY } from './types.js';
1
+ import { ENTITY_METADATA_KEY, ENTITY_OPTIONS_METADATA_KEY, ENTITY_VALIDATOR_METADATA_KEY } from './types.js';
2
2
  /**
3
3
  * Decorator that marks a class as an Entity.
4
4
  * This allows us to identify entity instances later.
5
5
  *
6
+ * @param options - Optional configuration for the entity
7
+ *
6
8
  * @example
7
9
  * ```typescript
8
10
  * @Entity()
9
11
  * class User {
10
12
  * name: string;
11
13
  * }
14
+ *
15
+ * @Entity({ collection: true })
16
+ * class Tags {
17
+ * @ArrayProperty(() => String)
18
+ * collection: string[];
19
+ * }
12
20
  * ```
13
- */ export function Entity() {
21
+ */ export function Entity(options = {}) {
14
22
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
15
23
  return function(target) {
16
24
  // Store metadata on the class constructor
17
25
  Reflect.defineMetadata(ENTITY_METADATA_KEY, true, target);
26
+ if (options) {
27
+ Reflect.defineMetadata(ENTITY_OPTIONS_METADATA_KEY, options, target);
28
+ }
18
29
  };
19
30
  }
31
+ /**
32
+ * Decorator that marks a class as a Collection Entity.
33
+ * This is syntax sugar for @Entity({ collection: true }).
34
+ *
35
+ * Collection entities must have a 'collection' property that is an array.
36
+ * When serialized with EntityUtils.toJSON(), they are unwrapped to just the array.
37
+ * When deserialized from an array, the array is wrapped in { collection: [...] }.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * @CollectionEntity()
42
+ * class Tags {
43
+ * @ArrayProperty(() => String)
44
+ * readonly collection: string[];
45
+ *
46
+ * constructor(data: { collection: string[] }) {
47
+ * this.collection = data.collection;
48
+ * }
49
+ * }
50
+ *
51
+ * @Entity()
52
+ * class Article {
53
+ * @EntityProperty(() => Tags)
54
+ * tags!: Tags;
55
+ * }
56
+ *
57
+ * const article = new Article(...);
58
+ * const json = EntityUtils.toJSON(article);
59
+ * // { tags: ["tag1", "tag2"] } - unwrapped to array
60
+ *
61
+ * // Also works when serializing the collection directly:
62
+ * const tagsJson = EntityUtils.toJSON(tags);
63
+ * // ["tag1", "tag2"] - unwrapped to array
64
+ * ```
65
+ */ export function CollectionEntity() {
66
+ return Entity({
67
+ collection: true
68
+ });
69
+ }
20
70
  /**
21
71
  * Decorator that marks a method as an entity validator.
22
72
  * The method should return an array of Problems.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/entity.ts"],"sourcesContent":["import { ENTITY_METADATA_KEY, ENTITY_VALIDATOR_METADATA_KEY } from './types.js';\n\n/**\n * Decorator that marks a class as an Entity.\n * This allows us to identify entity instances later.\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * name: string;\n * }\n * ```\n */\nexport function Entity(): ClassDecorator {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n return function (target: Function) {\n // Store metadata on the class constructor\n Reflect.defineMetadata(ENTITY_METADATA_KEY, true, target);\n };\n}\n\n/**\n * Decorator that marks a method as an entity validator.\n * The method should return an array of Problems.\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) firstName!: string;\n * @Property({ type: () => String }) lastName!: string;\n *\n * @EntityValidator()\n * validateNames(): Problem[] {\n * const problems: Problem[] = [];\n * if (this.firstName === this.lastName) {\n * problems.push(new Problem({\n * property: 'firstName',\n * message: 'First and last name cannot be the same'\n * }));\n * }\n * return problems;\n * }\n * }\n * ```\n */\nexport function EntityValidator(): MethodDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n if (typeof propertyKey !== 'string') {\n return;\n }\n\n const existingValidators: string[] =\n Reflect.getOwnMetadata(ENTITY_VALIDATOR_METADATA_KEY, target) || [];\n\n if (!existingValidators.includes(propertyKey)) {\n existingValidators.push(propertyKey);\n }\n\n Reflect.defineMetadata(\n ENTITY_VALIDATOR_METADATA_KEY,\n existingValidators,\n target,\n );\n };\n}\n"],"names":["ENTITY_METADATA_KEY","ENTITY_VALIDATOR_METADATA_KEY","Entity","target","Reflect","defineMetadata","EntityValidator","propertyKey","existingValidators","getOwnMetadata","includes","push"],"mappings":"AAAA,SAASA,mBAAmB,EAAEC,6BAA6B,QAAQ,aAAa;AAEhF;;;;;;;;;;;CAWC,GACD,OAAO,SAASC;IACd,sEAAsE;IACtE,OAAO,SAAUC,MAAgB;QAC/B,0CAA0C;QAC1CC,QAAQC,cAAc,CAACL,qBAAqB,MAAMG;IACpD;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASG;IACd,OAAO,CAACH,QAAgBI;QACtB,IAAI,OAAOA,gBAAgB,UAAU;YACnC;QACF;QAEA,MAAMC,qBACJJ,QAAQK,cAAc,CAACR,+BAA+BE,WAAW,EAAE;QAErE,IAAI,CAACK,mBAAmBE,QAAQ,CAACH,cAAc;YAC7CC,mBAAmBG,IAAI,CAACJ;QAC1B;QAEAH,QAAQC,cAAc,CACpBJ,+BACAO,oBACAL;IAEJ;AACF"}
1
+ {"version":3,"sources":["../../src/lib/entity.ts"],"sourcesContent":["import {\n ENTITY_METADATA_KEY,\n ENTITY_OPTIONS_METADATA_KEY,\n ENTITY_VALIDATOR_METADATA_KEY,\n} from './types.js';\n\n/**\n * Options for Entity decorator\n */\nexport interface EntityOptions {\n /**\n * Whether this entity represents a collection.\n * Collection entities must have a 'collection' property that is an array.\n * When serialized, collection entities are unwrapped to just their array.\n * When deserialized from an array, the array is wrapped in { collection: [...] }.\n */\n collection?: boolean;\n}\n\n/**\n * Decorator that marks a class as an Entity.\n * This allows us to identify entity instances later.\n *\n * @param options - Optional configuration for the entity\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * name: string;\n * }\n *\n * @Entity({ collection: true })\n * class Tags {\n * @ArrayProperty(() => String)\n * collection: string[];\n * }\n * ```\n */\nexport function Entity(options: EntityOptions = {}): ClassDecorator {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n return function (target: Function) {\n // Store metadata on the class constructor\n Reflect.defineMetadata(ENTITY_METADATA_KEY, true, target);\n if (options) {\n Reflect.defineMetadata(ENTITY_OPTIONS_METADATA_KEY, options, target);\n }\n };\n}\n\n/**\n * Decorator that marks a class as a Collection Entity.\n * This is syntax sugar for @Entity({ collection: true }).\n *\n * Collection entities must have a 'collection' property that is an array.\n * When serialized with EntityUtils.toJSON(), they are unwrapped to just the array.\n * When deserialized from an array, the array is wrapped in { collection: [...] }.\n *\n * @example\n * ```typescript\n * @CollectionEntity()\n * class Tags {\n * @ArrayProperty(() => String)\n * readonly collection: string[];\n *\n * constructor(data: { collection: string[] }) {\n * this.collection = data.collection;\n * }\n * }\n *\n * @Entity()\n * class Article {\n * @EntityProperty(() => Tags)\n * tags!: Tags;\n * }\n *\n * const article = new Article(...);\n * const json = EntityUtils.toJSON(article);\n * // { tags: [\"tag1\", \"tag2\"] } - unwrapped to array\n *\n * // Also works when serializing the collection directly:\n * const tagsJson = EntityUtils.toJSON(tags);\n * // [\"tag1\", \"tag2\"] - unwrapped to array\n * ```\n */\nexport function CollectionEntity(): ClassDecorator {\n return Entity({ collection: true });\n}\n\n/**\n * Decorator that marks a method as an entity validator.\n * The method should return an array of Problems.\n *\n * @example\n * ```typescript\n * @Entity()\n * class User {\n * @Property({ type: () => String }) firstName!: string;\n * @Property({ type: () => String }) lastName!: string;\n *\n * @EntityValidator()\n * validateNames(): Problem[] {\n * const problems: Problem[] = [];\n * if (this.firstName === this.lastName) {\n * problems.push(new Problem({\n * property: 'firstName',\n * message: 'First and last name cannot be the same'\n * }));\n * }\n * return problems;\n * }\n * }\n * ```\n */\nexport function EntityValidator(): MethodDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n if (typeof propertyKey !== 'string') {\n return;\n }\n\n const existingValidators: string[] =\n Reflect.getOwnMetadata(ENTITY_VALIDATOR_METADATA_KEY, target) || [];\n\n if (!existingValidators.includes(propertyKey)) {\n existingValidators.push(propertyKey);\n }\n\n Reflect.defineMetadata(\n ENTITY_VALIDATOR_METADATA_KEY,\n existingValidators,\n target,\n );\n };\n}\n"],"names":["ENTITY_METADATA_KEY","ENTITY_OPTIONS_METADATA_KEY","ENTITY_VALIDATOR_METADATA_KEY","Entity","options","target","Reflect","defineMetadata","CollectionEntity","collection","EntityValidator","propertyKey","existingValidators","getOwnMetadata","includes","push"],"mappings":"AAAA,SACEA,mBAAmB,EACnBC,2BAA2B,EAC3BC,6BAA6B,QACxB,aAAa;AAepB;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,SAASC,OAAOC,UAAyB,CAAC,CAAC;IAChD,sEAAsE;IACtE,OAAO,SAAUC,MAAgB;QAC/B,0CAA0C;QAC1CC,QAAQC,cAAc,CAACP,qBAAqB,MAAMK;QAClD,IAAID,SAAS;YACXE,QAAQC,cAAc,CAACN,6BAA6BG,SAASC;QAC/D;IACF;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCC,GACD,OAAO,SAASG;IACd,OAAOL,OAAO;QAAEM,YAAY;IAAK;AACnC;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASC;IACd,OAAO,CAACL,QAAgBM;QACtB,IAAI,OAAOA,gBAAgB,UAAU;YACnC;QACF;QAEA,MAAMC,qBACJN,QAAQO,cAAc,CAACX,+BAA+BG,WAAW,EAAE;QAErE,IAAI,CAACO,mBAAmBE,QAAQ,CAACH,cAAc;YAC7CC,mBAAmBG,IAAI,CAACJ;QAC1B;QAEAL,QAAQC,cAAc,CACpBL,+BACAU,oBACAP;IAEJ;AACF"}
@@ -11,6 +11,10 @@ export declare const PROPERTY_OPTIONS_METADATA_KEY: unique symbol;
11
11
  * Metadata key used to store entity information
12
12
  */
13
13
  export declare const ENTITY_METADATA_KEY: unique symbol;
14
+ /**
15
+ * Metadata key used to store entity options
16
+ */
17
+ export declare const ENTITY_OPTIONS_METADATA_KEY: unique symbol;
14
18
  /**
15
19
  * Metadata key used to store entity validator methods
16
20
  */
@@ -102,6 +106,21 @@ export interface PropertyOptions<T = any, C extends CtorLike<T> = AnyCtor<T> | B
102
106
  * id!: string; // This property cannot be updated
103
107
  */
104
108
  preventUpdates?: boolean;
109
+ /**
110
+ * Default value for the property when not provided during deserialization.
111
+ * Can be a static value or a function that returns the default value.
112
+ * The function can be async and return a Promise.
113
+ * @example
114
+ * @Property({ type: () => String, default: 'N/A' })
115
+ * nickname?: string;
116
+ *
117
+ * @Property({ type: () => Date, default: () => new Date() })
118
+ * createdAt!: Date;
119
+ *
120
+ * @Property({ type: () => String, default: async () => await fetchDefaultValue() })
121
+ * value!: string;
122
+ */
123
+ default?: InstanceOfCtorLike<C> | (() => InstanceOfCtorLike<C> | Promise<InstanceOfCtorLike<C>>);
105
124
  /**
106
125
  * Custom serialization function to convert the property value to JSON-compatible format.
107
126
  * Must be paired with deserialize - both must be defined together or both omitted.
@@ -227,4 +246,7 @@ export type SafeOperationResult<T> = Promise<{
227
246
  data: undefined;
228
247
  problems: Problem[];
229
248
  }>;
249
+ export type ParseOptions = {
250
+ strict?: boolean;
251
+ };
230
252
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"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"}
package/dist/lib/types.js CHANGED
@@ -7,6 +7,9 @@
7
7
  /**
8
8
  * Metadata key used to store entity information
9
9
  */ export const ENTITY_METADATA_KEY = Symbol('entity:metadata');
10
+ /**
11
+ * Metadata key used to store entity options
12
+ */ export const ENTITY_OPTIONS_METADATA_KEY = Symbol('entity:options:metadata');
10
13
  /**
11
14
  * Metadata key used to store entity validator methods
12
15
  */ export const ENTITY_VALIDATOR_METADATA_KEY = Symbol('entity:validator:metadata');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/types.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-function-type */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-wrapper-object-types */\nimport type { Problem } from './problem.js';\n\n/**\n * Metadata key used to store property information\n */\nexport const PROPERTY_METADATA_KEY = Symbol('property:metadata');\n\n/**\n * Metadata key used to store property options\n */\nexport const PROPERTY_OPTIONS_METADATA_KEY = Symbol(\n 'property:options:metadata',\n);\n\n/**\n * Metadata key used to store entity information\n */\nexport const ENTITY_METADATA_KEY = Symbol('entity:metadata');\n\n/**\n * Metadata key used to store entity validator methods\n */\nexport const ENTITY_VALIDATOR_METADATA_KEY = Symbol(\n 'entity:validator:metadata',\n);\n\n/**\n * Metadata key used to store injected property information\n */\nexport const INJECTED_PROPERTY_METADATA_KEY = Symbol(\n 'injected-property:metadata',\n);\n\n/**\n * Metadata key used to store injected property options (tokens)\n */\nexport const INJECTED_PROPERTY_OPTIONS_METADATA_KEY = Symbol(\n 'injected-property:options:metadata',\n);\n\nexport type AnyCtor<T = any> = Function & { prototype: T };\n\nexport type BuiltinCtors =\n | StringConstructor\n | NumberConstructor\n | BooleanConstructor\n | BigIntConstructor\n | SymbolConstructor\n | DateConstructor;\n\n/**\n * Type constructors for primitive types that can be deserialized\n * (excludes Symbol which cannot be deserialized from JSON)\n */\nexport type PrimitiveConstructor =\n | StringConstructor\n | NumberConstructor\n | BooleanConstructor\n | BigIntConstructor\n | DateConstructor;\n\nexport type CtorLike<T> = AnyCtor<T> | BuiltinCtors;\n\nexport type InstanceOfCtorLike<C> = C extends StringConstructor\n ? string\n : C extends NumberConstructor\n ? number\n : C extends BooleanConstructor\n ? boolean\n : C extends BigIntConstructor\n ? bigint\n : C extends SymbolConstructor\n ? symbol\n : C extends DateConstructor\n ? Date\n : C extends AnyCtor<infer T>\n ? T\n : never;\n\n/**\n * Options for the Property decorator\n */\nexport interface PropertyOptions<\n T = any,\n C extends CtorLike<T> = AnyCtor<T> | BuiltinCtors,\n> {\n /**\n * Custom equality comparison function for this property\n * @param a - First value to compare\n * @param b - Second value to compare\n * @returns true if values are equal, false otherwise\n */\n equals?: (a: InstanceOfCtorLike<C>, b: InstanceOfCtorLike<C>) => boolean;\n\n /**\n * Type constructor for this property. Required for EntityUtils.parse() support.\n * Use a function that returns the type constructor to support forward references.\n * @example\n * @Property({ type: () => String })\n * name!: string;\n *\n * @Property({ type: () => Address })\n * address!: Address;\n */\n type: () => C;\n\n /**\n * Whether this property is an array. Defaults to false.\n * When true, the deserializer will map over array elements.\n * @example\n * @Property({ type: () => String, array: true })\n * tags!: string[];\n */\n array?: boolean;\n\n /**\n * Whether this property is optional. Defaults to false.\n * When true, the property can be undefined or null.\n * When false, the property must be present and not null/undefined.\n * @example\n * @Property({ type: () => String, optional: true })\n * nickname?: string;\n */\n optional?: boolean;\n\n /**\n * Whether the array can contain null/undefined elements. Defaults to false.\n * Only applicable when array is true.\n * When false (default), null/undefined elements will cause an error.\n * When true, null/undefined elements are allowed in the array.\n * @example\n * @Property({ type: () => String, array: true, sparse: true })\n * tags!: (string | null)[];\n */\n sparse?: boolean;\n\n /**\n * Whether to bypass type validation and pass values through as-is.\n * Use this for generic types like Record<string, unknown> or any.\n * When true, no type checking or transformation is performed.\n * Also bypasses any custom serialize/deserialize callbacks.\n * @example\n * @Property({ passthrough: true })\n * metadata!: Record<string, unknown>;\n */\n passthrough?: boolean;\n\n /**\n * Whether to prevent this property from being updated via EntityUtils.update().\n * When true, this property cannot be modified through update operations.\n * Defaults to false, allowing the property to be updated.\n * @example\n * @Property({ type: () => String, preventUpdates: true })\n * id!: string; // This property cannot be updated\n */\n preventUpdates?: boolean;\n\n /**\n * Custom serialization function to convert the property value to JSON-compatible format.\n * Must be paired with deserialize - both must be defined together or both omitted.\n * Not used when passthrough is true.\n * @example\n * @Property({\n * type: () => MyClass,\n * serialize: (value) => ({ data: value.toData() }),\n * deserialize: (json) => MyClass.fromData(json.data)\n * })\n * myProperty!: MyClass;\n */\n serialize?: (value: InstanceOfCtorLike<C>) => unknown;\n\n /**\n * Custom deserialization function to convert JSON data back to the property type.\n * Must be paired with serialize - both must be defined together or both omitted.\n * Not used when passthrough is true.\n * @example\n * @Property({\n * type: () => MyClass,\n * serialize: (value) => ({ data: value.toData() }),\n * deserialize: (json) => MyClass.fromData(json.data)\n * })\n * myProperty!: MyClass;\n */\n deserialize?: (serialized: unknown) => InstanceOfCtorLike<C>;\n\n /**\n * Array of validator functions for this property.\n * Each validator receives the property value and validation context.\n * Empty array means validation passed.\n * If the property is an array (array: true), these validators will run against each item.\n * Use arrayValidators instead to validate the array as a whole.\n * If passthrough is true, validators will run against the raw value.\n * @example\n * @Property({\n * type: () => String,\n * validators: [\n * (value, { createProblem }) =>\n * value.length > 10 ? [createProblem('Too long')] : []\n * ]\n * })\n * name!: string;\n */\n validators?: PropertyValidator<InstanceOfCtorLike<C>>[];\n\n /**\n * Array of validator functions for this property when it is an array.\n * Each validator receives the array value and validation context.\n * Empty array means validation passed.\n * Only applicable when array is true.\n * Not applicable when passthrough is true.\n * @example\n * @Property({\n * type: () => Number,\n * array: true,\n * arrayValidators: [\n * (value, { createProblem }) =>\n * value.length === 0 ? [createProblem('Array cannot be empty')] : []\n * ]\n * })\n * scores!: number[];\n */\n arrayValidators?: PropertyValidator<InstanceOfCtorLike<C>[]>[];\n}\n\n/**\n * A validator function for a property.\n * The validator receives the value and returns Problems with property paths relative to the value.\n * Can be synchronous or asynchronous.\n * The calling code will prepend the actual property key to all returned problems.\n *\n * @param data - Object containing the value to validate\n * @param data.value - The value to validate\n * @returns Array of Problems (empty if valid) or Promise resolving to Problems.\n * Problems should have empty property for the value itself,\n * or relative paths for nested properties (e.g., 'name', '[0]', 'address.street')\n *\n * @example\n * ```typescript\n * // Synchronous validator\n * (({ value }) =>\n * value.length < 3 ? [new Problem({ property: '', message: 'Too short' })] : [])\n *\n * // Asynchronous validator\n * async ({ value }) => {\n * const exists = await checkDatabase(value);\n * return exists ? [] : [new Problem({ property: '', message: 'Not found' })];\n * }\n * ```\n */\nexport type PropertyValidator<T> = (data: {\n value: T;\n}) => Problem[] | Promise<Problem[]>;\n\n/**\n * A validator function for an entity.\n * Can be synchronous or asynchronous.\n * @param instance - The entity instance to validate\n * @returns Array of Problems (empty if valid) or Promise resolving to Problems\n */\nexport type EntityValidatorFn<T = any> = (\n instance: T,\n) => Problem[] | Promise<Problem[]>;\n\nexport interface Type<T = any> extends Function {\n new (...args: any[]): T;\n}\n\nexport type EntityDIToken<T = any> =\n | string\n | symbol\n | Function\n | AnyCtor<T>\n | Type<T>;\n\nexport type EntityDIProvider<T = any> =\n | {\n provide: EntityDIToken<T>;\n useValue?: T;\n useFactory?: never;\n }\n | {\n provide: EntityDIToken<T>;\n useValue?: never;\n useFactory?: () => T | Promise<T>;\n };\n\nexport type EntityDIFallbackFn = (token: EntityDIToken) => Promise<any>;\n\n/**\n * Generic type for safe operation results that can succeed or fail\n * @template T - The data type on success\n */\nexport type SafeOperationResult<T> = Promise<\n | { success: true; data: T; problems: Problem[] }\n | { success: false; data: undefined; problems: Problem[] }\n>;\n"],"names":["PROPERTY_METADATA_KEY","Symbol","PROPERTY_OPTIONS_METADATA_KEY","ENTITY_METADATA_KEY","ENTITY_VALIDATOR_METADATA_KEY","INJECTED_PROPERTY_METADATA_KEY","INJECTED_PROPERTY_OPTIONS_METADATA_KEY"],"mappings":"AAAA,6DAA6D,GAC7D,qDAAqD,GACrD,6DAA6D,GAG7D;;CAEC,GACD,OAAO,MAAMA,wBAAwBC,OAAO,qBAAqB;AAEjE;;CAEC,GACD,OAAO,MAAMC,gCAAgCD,OAC3C,6BACA;AAEF;;CAEC,GACD,OAAO,MAAME,sBAAsBF,OAAO,mBAAmB;AAE7D;;CAEC,GACD,OAAO,MAAMG,gCAAgCH,OAC3C,6BACA;AAEF;;CAEC,GACD,OAAO,MAAMI,iCAAiCJ,OAC5C,8BACA;AAEF;;CAEC,GACD,OAAO,MAAMK,yCAAyCL,OACpD,sCACA"}
1
+ {"version":3,"sources":["../../src/lib/types.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-function-type */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-wrapper-object-types */\nimport type { Problem } from './problem.js';\n\n/**\n * Metadata key used to store property information\n */\nexport const PROPERTY_METADATA_KEY = Symbol('property:metadata');\n\n/**\n * Metadata key used to store property options\n */\nexport const PROPERTY_OPTIONS_METADATA_KEY = Symbol(\n 'property:options:metadata',\n);\n\n/**\n * Metadata key used to store entity information\n */\nexport const ENTITY_METADATA_KEY = Symbol('entity:metadata');\n\n/**\n * Metadata key used to store entity options\n */\nexport const ENTITY_OPTIONS_METADATA_KEY = Symbol('entity:options:metadata');\n\n/**\n * Metadata key used to store entity validator methods\n */\nexport const ENTITY_VALIDATOR_METADATA_KEY = Symbol(\n 'entity:validator:metadata',\n);\n\n/**\n * Metadata key used to store injected property information\n */\nexport const INJECTED_PROPERTY_METADATA_KEY = Symbol(\n 'injected-property:metadata',\n);\n\n/**\n * Metadata key used to store injected property options (tokens)\n */\nexport const INJECTED_PROPERTY_OPTIONS_METADATA_KEY = Symbol(\n 'injected-property:options:metadata',\n);\n\nexport type AnyCtor<T = any> = Function & { prototype: T };\n\nexport type BuiltinCtors =\n | StringConstructor\n | NumberConstructor\n | BooleanConstructor\n | BigIntConstructor\n | SymbolConstructor\n | DateConstructor;\n\n/**\n * Type constructors for primitive types that can be deserialized\n * (excludes Symbol which cannot be deserialized from JSON)\n */\nexport type PrimitiveConstructor =\n | StringConstructor\n | NumberConstructor\n | BooleanConstructor\n | BigIntConstructor\n | DateConstructor;\n\nexport type CtorLike<T> = AnyCtor<T> | BuiltinCtors;\n\nexport type InstanceOfCtorLike<C> = C extends StringConstructor\n ? string\n : C extends NumberConstructor\n ? number\n : C extends BooleanConstructor\n ? boolean\n : C extends BigIntConstructor\n ? bigint\n : C extends SymbolConstructor\n ? symbol\n : C extends DateConstructor\n ? Date\n : C extends AnyCtor<infer T>\n ? T\n : never;\n\n/**\n * Options for the Property decorator\n */\nexport interface PropertyOptions<\n T = any,\n C extends CtorLike<T> = AnyCtor<T> | BuiltinCtors,\n> {\n /**\n * Custom equality comparison function for this property\n * @param a - First value to compare\n * @param b - Second value to compare\n * @returns true if values are equal, false otherwise\n */\n equals?: (a: InstanceOfCtorLike<C>, b: InstanceOfCtorLike<C>) => boolean;\n\n /**\n * Type constructor for this property. Required for EntityUtils.parse() support.\n * Use a function that returns the type constructor to support forward references.\n * @example\n * @Property({ type: () => String })\n * name!: string;\n *\n * @Property({ type: () => Address })\n * address!: Address;\n */\n type: () => C;\n\n /**\n * Whether this property is an array. Defaults to false.\n * When true, the deserializer will map over array elements.\n * @example\n * @Property({ type: () => String, array: true })\n * tags!: string[];\n */\n array?: boolean;\n\n /**\n * Whether this property is optional. Defaults to false.\n * When true, the property can be undefined or null.\n * When false, the property must be present and not null/undefined.\n * @example\n * @Property({ type: () => String, optional: true })\n * nickname?: string;\n */\n optional?: boolean;\n\n /**\n * Whether the array can contain null/undefined elements. Defaults to false.\n * Only applicable when array is true.\n * When false (default), null/undefined elements will cause an error.\n * When true, null/undefined elements are allowed in the array.\n * @example\n * @Property({ type: () => String, array: true, sparse: true })\n * tags!: (string | null)[];\n */\n sparse?: boolean;\n\n /**\n * Whether to bypass type validation and pass values through as-is.\n * Use this for generic types like Record<string, unknown> or any.\n * When true, no type checking or transformation is performed.\n * Also bypasses any custom serialize/deserialize callbacks.\n * @example\n * @Property({ passthrough: true })\n * metadata!: Record<string, unknown>;\n */\n passthrough?: boolean;\n\n /**\n * Whether to prevent this property from being updated via EntityUtils.update().\n * When true, this property cannot be modified through update operations.\n * Defaults to false, allowing the property to be updated.\n * @example\n * @Property({ type: () => String, preventUpdates: true })\n * id!: string; // This property cannot be updated\n */\n preventUpdates?: boolean;\n\n /**\n * Default value for the property when not provided during deserialization.\n * Can be a static value or a function that returns the default value.\n * The function can be async and return a Promise.\n * @example\n * @Property({ type: () => String, default: 'N/A' })\n * nickname?: string;\n *\n * @Property({ type: () => Date, default: () => new Date() })\n * createdAt!: Date;\n *\n * @Property({ type: () => String, default: async () => await fetchDefaultValue() })\n * value!: string;\n */\n default?:\n | InstanceOfCtorLike<C>\n | (() => InstanceOfCtorLike<C> | Promise<InstanceOfCtorLike<C>>);\n\n /**\n * Custom serialization function to convert the property value to JSON-compatible format.\n * Must be paired with deserialize - both must be defined together or both omitted.\n * Not used when passthrough is true.\n * @example\n * @Property({\n * type: () => MyClass,\n * serialize: (value) => ({ data: value.toData() }),\n * deserialize: (json) => MyClass.fromData(json.data)\n * })\n * myProperty!: MyClass;\n */\n serialize?: (value: InstanceOfCtorLike<C>) => unknown;\n\n /**\n * Custom deserialization function to convert JSON data back to the property type.\n * Must be paired with serialize - both must be defined together or both omitted.\n * Not used when passthrough is true.\n * @example\n * @Property({\n * type: () => MyClass,\n * serialize: (value) => ({ data: value.toData() }),\n * deserialize: (json) => MyClass.fromData(json.data)\n * })\n * myProperty!: MyClass;\n */\n deserialize?: (serialized: unknown) => InstanceOfCtorLike<C>;\n\n /**\n * Array of validator functions for this property.\n * Each validator receives the property value and validation context.\n * Empty array means validation passed.\n * If the property is an array (array: true), these validators will run against each item.\n * Use arrayValidators instead to validate the array as a whole.\n * If passthrough is true, validators will run against the raw value.\n * @example\n * @Property({\n * type: () => String,\n * validators: [\n * (value, { createProblem }) =>\n * value.length > 10 ? [createProblem('Too long')] : []\n * ]\n * })\n * name!: string;\n */\n validators?: PropertyValidator<InstanceOfCtorLike<C>>[];\n\n /**\n * Array of validator functions for this property when it is an array.\n * Each validator receives the array value and validation context.\n * Empty array means validation passed.\n * Only applicable when array is true.\n * Not applicable when passthrough is true.\n * @example\n * @Property({\n * type: () => Number,\n * array: true,\n * arrayValidators: [\n * (value, { createProblem }) =>\n * value.length === 0 ? [createProblem('Array cannot be empty')] : []\n * ]\n * })\n * scores!: number[];\n */\n arrayValidators?: PropertyValidator<InstanceOfCtorLike<C>[]>[];\n}\n\n/**\n * A validator function for a property.\n * The validator receives the value and returns Problems with property paths relative to the value.\n * Can be synchronous or asynchronous.\n * The calling code will prepend the actual property key to all returned problems.\n *\n * @param data - Object containing the value to validate\n * @param data.value - The value to validate\n * @returns Array of Problems (empty if valid) or Promise resolving to Problems.\n * Problems should have empty property for the value itself,\n * or relative paths for nested properties (e.g., 'name', '[0]', 'address.street')\n *\n * @example\n * ```typescript\n * // Synchronous validator\n * (({ value }) =>\n * value.length < 3 ? [new Problem({ property: '', message: 'Too short' })] : [])\n *\n * // Asynchronous validator\n * async ({ value }) => {\n * const exists = await checkDatabase(value);\n * return exists ? [] : [new Problem({ property: '', message: 'Not found' })];\n * }\n * ```\n */\nexport type PropertyValidator<T> = (data: {\n value: T;\n}) => Problem[] | Promise<Problem[]>;\n\n/**\n * A validator function for an entity.\n * Can be synchronous or asynchronous.\n * @param instance - The entity instance to validate\n * @returns Array of Problems (empty if valid) or Promise resolving to Problems\n */\nexport type EntityValidatorFn<T = any> = (\n instance: T,\n) => Problem[] | Promise<Problem[]>;\n\nexport interface Type<T = any> extends Function {\n new (...args: any[]): T;\n}\n\nexport type EntityDIToken<T = any> =\n | string\n | symbol\n | Function\n | AnyCtor<T>\n | Type<T>;\n\nexport type EntityDIProvider<T = any> =\n | {\n provide: EntityDIToken<T>;\n useValue?: T;\n useFactory?: never;\n }\n | {\n provide: EntityDIToken<T>;\n useValue?: never;\n useFactory?: () => T | Promise<T>;\n };\n\nexport type EntityDIFallbackFn = (token: EntityDIToken) => Promise<any>;\n\n/**\n * Generic type for safe operation results that can succeed or fail\n * @template T - The data type on success\n */\nexport type SafeOperationResult<T> = Promise<\n | { success: true; data: T; problems: Problem[] }\n | { success: false; data: undefined; problems: Problem[] }\n>;\n\nexport type ParseOptions = { strict?: boolean };\n"],"names":["PROPERTY_METADATA_KEY","Symbol","PROPERTY_OPTIONS_METADATA_KEY","ENTITY_METADATA_KEY","ENTITY_OPTIONS_METADATA_KEY","ENTITY_VALIDATOR_METADATA_KEY","INJECTED_PROPERTY_METADATA_KEY","INJECTED_PROPERTY_OPTIONS_METADATA_KEY"],"mappings":"AAAA,6DAA6D,GAC7D,qDAAqD,GACrD,6DAA6D,GAG7D;;CAEC,GACD,OAAO,MAAMA,wBAAwBC,OAAO,qBAAqB;AAEjE;;CAEC,GACD,OAAO,MAAMC,gCAAgCD,OAC3C,6BACA;AAEF;;CAEC,GACD,OAAO,MAAME,sBAAsBF,OAAO,mBAAmB;AAE7D;;CAEC,GACD,OAAO,MAAMG,8BAA8BH,OAAO,2BAA2B;AAE7E;;CAEC,GACD,OAAO,MAAMI,gCAAgCJ,OAC3C,6BACA;AAEF;;CAEC,GACD,OAAO,MAAMK,iCAAiCL,OAC5C,8BACA;AAEF;;CAEC,GACD,OAAO,MAAMM,yCAAyCN,OACpD,sCACA"}
@@ -1 +1 @@
1
- {"version":3,"file":"zod-property.d.ts","sourceRoot":"","sources":["../../src/lib/zod-property.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGrE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA6B7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,GAAG,EACjC,MAAM,EAAE,CAAC,CAAC,UAAU,EACpB,OAAO,CAAC,EAAE,IAAI,CACZ,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,EACvB,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,aAAa,GAAG,YAAY,CACpE,GAAG;IACF,UAAU,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;CACrC,GACA,iBAAiB,CAiBnB"}
1
+ {"version":3,"file":"zod-property.d.ts","sourceRoot":"","sources":["../../src/lib/zod-property.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGrE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA6B7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,wBAAgB,WAAW,CAAC,CAAC,GAAG,GAAG,EACjC,MAAM,EAAE,CAAC,CAAC,UAAU,EACpB,OAAO,CAAC,EAAE,IAAI,CACZ,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,EACvB,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,aAAa,GAAG,YAAY,CACpE,GAAG;IACF,UAAU,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;CACrC,GACA,iBAAiB,CAkBnB"}
@@ -52,9 +52,11 @@ import { ValidationError } from './validation-error.js';
52
52
  * email?: string;
53
53
  * }
54
54
  * ```
55
- */ export function ZodProperty(schema, options) {
55
+ */ // TODO: fix type inference
56
+ export function ZodProperty(schema, options) {
56
57
  return Property({
57
58
  ...options,
59
+ default: options?.default,
58
60
  type: ()=>Object,
59
61
  serialize: (value)=>value,
60
62
  deserialize: (value)=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/zod-property.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Property } from './property.js';\nimport type { PropertyOptions, PropertyValidator } from './types.js';\nimport { Problem } from './problem.js';\nimport { ValidationError } from './validation-error.js';\nimport type { z } from 'zod';\n\n/**\n * Converts Zod issues to Problem array\n * @param issues - Zod validation issues\n * @returns Array of Problems with proper property paths\n */\nfunction zodIssuesToProblems(issues: z.core.$ZodIssue[]): Problem[] {\n return issues.map((issue: any) => {\n const path = issue.path || [];\n\n let propertyPath = '';\n for (const segment of path) {\n if (typeof segment === 'number') {\n propertyPath += `[${segment}]`;\n } else if (propertyPath === '') {\n propertyPath = segment;\n } else {\n propertyPath += `.${segment}`;\n }\n }\n\n return new Problem({\n property: propertyPath,\n message: issue.message || 'Validation failed',\n });\n });\n}\n\n/**\n * Helper decorator for Zod schema properties.\n * Validates values using a Zod schema during deserialization.\n * Validation failures throw ValidationError (hard errors).\n *\n * When validation succeeds, applies any Zod transformations to the value.\n *\n * @param schema - The Zod schema to validate against\n * @param options - Additional property options (optional, array, etc.)\n *\n * @example\n * ```typescript\n * import { z } from 'zod';\n *\n * const UserSchema = z.object({\n * name: z.string().min(3),\n * age: z.number().int().min(0)\n * });\n *\n * @Entity()\n * class User {\n * @ZodProperty(UserSchema)\n * data!: z.infer<typeof UserSchema>;\n *\n * @ZodProperty(z.string().email(), { optional: true })\n * email?: string;\n * }\n * ```\n */\nexport function ZodProperty<T = any>(\n schema: z.ZodTypeAny,\n options?: Omit<\n PropertyOptions<T, any>,\n 'type' | 'deserialize' | 'serialize' | 'passthrough' | 'validators'\n > & {\n validators?: PropertyValidator<T>[];\n },\n): PropertyDecorator {\n return Property({\n ...options,\n type: () => Object,\n serialize: (value: any) => value,\n deserialize: (value: unknown): any => {\n const result = schema.safeParse(value);\n\n if (result.success) {\n return result.data as T;\n } else {\n const problems = zodIssuesToProblems(result.error.issues);\n throw new ValidationError(problems);\n }\n },\n validators: options?.validators as any,\n });\n}\n"],"names":["Property","Problem","ValidationError","zodIssuesToProblems","issues","map","issue","path","propertyPath","segment","property","message","ZodProperty","schema","options","type","Object","serialize","value","deserialize","result","safeParse","success","data","problems","error","validators"],"mappings":"AAAA,qDAAqD,GACrD,SAASA,QAAQ,QAAQ,gBAAgB;AAEzC,SAASC,OAAO,QAAQ,eAAe;AACvC,SAASC,eAAe,QAAQ,wBAAwB;AAGxD;;;;CAIC,GACD,SAASC,oBAAoBC,MAA0B;IACrD,OAAOA,OAAOC,GAAG,CAAC,CAACC;QACjB,MAAMC,OAAOD,MAAMC,IAAI,IAAI,EAAE;QAE7B,IAAIC,eAAe;QACnB,KAAK,MAAMC,WAAWF,KAAM;YAC1B,IAAI,OAAOE,YAAY,UAAU;gBAC/BD,gBAAgB,CAAC,CAAC,EAAEC,QAAQ,CAAC,CAAC;YAChC,OAAO,IAAID,iBAAiB,IAAI;gBAC9BA,eAAeC;YACjB,OAAO;gBACLD,gBAAgB,CAAC,CAAC,EAAEC,SAAS;YAC/B;QACF;QAEA,OAAO,IAAIR,QAAQ;YACjBS,UAAUF;YACVG,SAASL,MAAMK,OAAO,IAAI;QAC5B;IACF;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,SAASC,YACdC,MAAoB,EACpBC,OAKC;IAED,OAAOd,SAAS;QACd,GAAGc,OAAO;QACVC,MAAM,IAAMC;QACZC,WAAW,CAACC,QAAeA;QAC3BC,aAAa,CAACD;YACZ,MAAME,SAASP,OAAOQ,SAAS,CAACH;YAEhC,IAAIE,OAAOE,OAAO,EAAE;gBAClB,OAAOF,OAAOG,IAAI;YACpB,OAAO;gBACL,MAAMC,WAAWrB,oBAAoBiB,OAAOK,KAAK,CAACrB,MAAM;gBACxD,MAAM,IAAIF,gBAAgBsB;YAC5B;QACF;QACAE,YAAYZ,SAASY;IACvB;AACF"}
1
+ {"version":3,"sources":["../../src/lib/zod-property.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Property } from './property.js';\nimport type { PropertyOptions, PropertyValidator } from './types.js';\nimport { Problem } from './problem.js';\nimport { ValidationError } from './validation-error.js';\nimport type { z } from 'zod';\n\n/**\n * Converts Zod issues to Problem array\n * @param issues - Zod validation issues\n * @returns Array of Problems with proper property paths\n */\nfunction zodIssuesToProblems(issues: z.core.$ZodIssue[]): Problem[] {\n return issues.map((issue: any) => {\n const path = issue.path || [];\n\n let propertyPath = '';\n for (const segment of path) {\n if (typeof segment === 'number') {\n propertyPath += `[${segment}]`;\n } else if (propertyPath === '') {\n propertyPath = segment;\n } else {\n propertyPath += `.${segment}`;\n }\n }\n\n return new Problem({\n property: propertyPath,\n message: issue.message || 'Validation failed',\n });\n });\n}\n\n/**\n * Helper decorator for Zod schema properties.\n * Validates values using a Zod schema during deserialization.\n * Validation failures throw ValidationError (hard errors).\n *\n * When validation succeeds, applies any Zod transformations to the value.\n *\n * @param schema - The Zod schema to validate against\n * @param options - Additional property options (optional, array, etc.)\n *\n * @example\n * ```typescript\n * import { z } from 'zod';\n *\n * const UserSchema = z.object({\n * name: z.string().min(3),\n * age: z.number().int().min(0)\n * });\n *\n * @Entity()\n * class User {\n * @ZodProperty(UserSchema)\n * data!: z.infer<typeof UserSchema>;\n *\n * @ZodProperty(z.string().email(), { optional: true })\n * email?: string;\n * }\n * ```\n */\n// TODO: fix type inference\nexport function ZodProperty<T = any>(\n schema: z.ZodTypeAny,\n options?: Omit<\n PropertyOptions<T, any>,\n 'type' | 'deserialize' | 'serialize' | 'passthrough' | 'validators'\n > & {\n validators?: PropertyValidator<T>[];\n },\n): PropertyDecorator {\n return Property({\n ...options,\n default: options?.default as any,\n type: () => Object,\n serialize: (value: any) => value,\n deserialize: (value: unknown): any => {\n const result = schema.safeParse(value);\n\n if (result.success) {\n return result.data as T;\n } else {\n const problems = zodIssuesToProblems(result.error.issues);\n throw new ValidationError(problems);\n }\n },\n validators: options?.validators as any,\n });\n}\n"],"names":["Property","Problem","ValidationError","zodIssuesToProblems","issues","map","issue","path","propertyPath","segment","property","message","ZodProperty","schema","options","default","type","Object","serialize","value","deserialize","result","safeParse","success","data","problems","error","validators"],"mappings":"AAAA,qDAAqD,GACrD,SAASA,QAAQ,QAAQ,gBAAgB;AAEzC,SAASC,OAAO,QAAQ,eAAe;AACvC,SAASC,eAAe,QAAQ,wBAAwB;AAGxD;;;;CAIC,GACD,SAASC,oBAAoBC,MAA0B;IACrD,OAAOA,OAAOC,GAAG,CAAC,CAACC;QACjB,MAAMC,OAAOD,MAAMC,IAAI,IAAI,EAAE;QAE7B,IAAIC,eAAe;QACnB,KAAK,MAAMC,WAAWF,KAAM;YAC1B,IAAI,OAAOE,YAAY,UAAU;gBAC/BD,gBAAgB,CAAC,CAAC,EAAEC,QAAQ,CAAC,CAAC;YAChC,OAAO,IAAID,iBAAiB,IAAI;gBAC9BA,eAAeC;YACjB,OAAO;gBACLD,gBAAgB,CAAC,CAAC,EAAEC,SAAS;YAC/B;QACF;QAEA,OAAO,IAAIR,QAAQ;YACjBS,UAAUF;YACVG,SAASL,MAAMK,OAAO,IAAI;QAC5B;IACF;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,2BAA2B;AAC3B,OAAO,SAASC,YACdC,MAAoB,EACpBC,OAKC;IAED,OAAOd,SAAS;QACd,GAAGc,OAAO;QACVC,SAASD,SAASC;QAClBC,MAAM,IAAMC;QACZC,WAAW,CAACC,QAAeA;QAC3BC,aAAa,CAACD;YACZ,MAAME,SAASR,OAAOS,SAAS,CAACH;YAEhC,IAAIE,OAAOE,OAAO,EAAE;gBAClB,OAAOF,OAAOG,IAAI;YACpB,OAAO;gBACL,MAAMC,WAAWtB,oBAAoBkB,OAAOK,KAAK,CAACtB,MAAM;gBACxD,MAAM,IAAIF,gBAAgBuB;YAC5B;QACF;QACAE,YAAYb,SAASa;IACvB;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtpaulino/entity",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",