@rtpaulino/entity 0.13.0 → 0.14.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.
Files changed (37) hide show
  1. package/dist/index.d.ts +4 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +4 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/entity-utils.d.ts +86 -1
  6. package/dist/lib/entity-utils.d.ts.map +1 -1
  7. package/dist/lib/entity-utils.js +270 -60
  8. package/dist/lib/entity-utils.js.map +1 -1
  9. package/dist/lib/entity.d.ts +26 -0
  10. package/dist/lib/entity.d.ts.map +1 -1
  11. package/dist/lib/entity.js +37 -1
  12. package/dist/lib/entity.js.map +1 -1
  13. package/dist/lib/primitive-deserializers.d.ts +15 -0
  14. package/dist/lib/primitive-deserializers.d.ts.map +1 -0
  15. package/dist/lib/primitive-deserializers.js +87 -0
  16. package/dist/lib/primitive-deserializers.js.map +1 -0
  17. package/dist/lib/problem.d.ts +14 -0
  18. package/dist/lib/problem.d.ts.map +1 -0
  19. package/dist/lib/problem.js +31 -0
  20. package/dist/lib/problem.js.map +1 -0
  21. package/dist/lib/property.d.ts +12 -0
  22. package/dist/lib/property.d.ts.map +1 -1
  23. package/dist/lib/property.js +26 -1
  24. package/dist/lib/property.js.map +1 -1
  25. package/dist/lib/types.d.ts +79 -0
  26. package/dist/lib/types.d.ts.map +1 -1
  27. package/dist/lib/types.js +3 -0
  28. package/dist/lib/types.js.map +1 -1
  29. package/dist/lib/validation-error.d.ts +9 -0
  30. package/dist/lib/validation-error.d.ts.map +1 -0
  31. package/dist/lib/validation-error.js +12 -0
  32. package/dist/lib/validation-error.js.map +1 -0
  33. package/dist/lib/validation-utils.d.ts +86 -0
  34. package/dist/lib/validation-utils.d.ts.map +1 -0
  35. package/dist/lib/validation-utils.js +112 -0
  36. package/dist/lib/validation-utils.js.map +1 -0
  37. package/package.json +1 -1
@@ -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 PROPERTY_METADATA_KEY,\n PROPERTY_OPTIONS_METADATA_KEY,\n PropertyOptions,\n} from './types.js';\nimport { isEqualWith } from 'lodash-es';\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 * @returns 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 * @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 = EntityUtils.parse(User, json);\n * ```\n */\n static parse<T extends object>(\n entityClass: new (data: any) => T,\n plainObject: Record<string, unknown>,\n ): T {\n const keys = this.getPropertyKeys(entityClass.prototype);\n const data: Record<string, unknown> = {};\n\n for (const key of keys) {\n const options = this.getPropertyOptions(entityClass.prototype, key);\n\n if (!options) {\n throw new Error(\n `Property '${key}' has no metadata. This should not happen if @Property() was used correctly.`,\n );\n }\n\n if (options.passthrough === true) {\n const value = plainObject[key];\n data[key] = value;\n continue;\n }\n\n const value = plainObject[key];\n const isOptional = options.optional === true;\n\n if (!(key in plainObject)) {\n if (!isOptional) {\n throw new Error(\n `Property '${key}' is required but missing from input`,\n );\n }\n continue;\n }\n\n if (value === null || value === undefined) {\n if (!isOptional) {\n throw new Error(`Property '${key}' cannot be null or undefined`);\n }\n data[key] = value;\n continue;\n }\n\n data[key] = this.deserializeValue(value, options, key);\n }\n\n return new entityClass(data as Partial<T>);\n }\n\n /**\n * Deserializes a single value according to the type metadata\n * @private\n */\n private static deserializeValue(\n value: unknown,\n options: PropertyOptions,\n propertyKey: string,\n ): 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 new Error(\n `Property '${propertyKey}' expects an array but received ${typeof value}`,\n );\n }\n\n return value.map((item, index) => {\n if (item === null || item === undefined) {\n if (!isSparse) {\n throw new Error(\n `Property '${propertyKey}[${index}]' cannot be null or undefined. Use sparse: true to allow null/undefined elements in arrays.`,\n );\n }\n return item;\n }\n if (options.deserialize) {\n return options.deserialize(item);\n }\n return this.deserializeSingleValue(\n item,\n typeConstructor,\n `${propertyKey}[${index}]`,\n );\n });\n }\n\n if (options.deserialize) {\n return options.deserialize(value);\n }\n\n return this.deserializeSingleValue(value, typeConstructor, propertyKey);\n }\n\n /**\n * Deserializes a single non-array value\n * @private\n */\n private static deserializeSingleValue(\n value: unknown,\n typeConstructor: any,\n propertyKey: string,\n ): unknown {\n if (typeConstructor === String) {\n if (typeof value !== 'string') {\n throw new Error(\n `Property '${propertyKey}' expects a string but received ${typeof value}`,\n );\n }\n return value;\n }\n\n if (typeConstructor === Number) {\n if (typeof value !== 'number') {\n throw new Error(\n `Property '${propertyKey}' expects a number but received ${typeof value}`,\n );\n }\n return value;\n }\n\n if (typeConstructor === Boolean) {\n if (typeof value !== 'boolean') {\n throw new Error(\n `Property '${propertyKey}' expects a boolean but received ${typeof value}`,\n );\n }\n return value;\n }\n\n if (typeConstructor === BigInt) {\n if (typeof value === 'bigint') {\n return value;\n }\n if (typeof value === 'string') {\n try {\n return BigInt(value);\n } catch {\n throw new Error(\n `Property '${propertyKey}' cannot parse '${value}' as BigInt`,\n );\n }\n }\n throw new Error(\n `Property '${propertyKey}' expects a bigint or string but received ${typeof value}`,\n );\n }\n\n if (typeConstructor === Date) {\n if (value instanceof Date) {\n return value;\n }\n if (typeof value === 'string') {\n const date = new Date(value);\n if (isNaN(date.getTime())) {\n throw new Error(\n `Property '${propertyKey}' cannot parse '${value}' as Date`,\n );\n }\n return date;\n }\n throw new Error(\n `Property '${propertyKey}' expects a Date or ISO string but received ${typeof value}`,\n );\n }\n\n if (this.isEntity(typeConstructor)) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(\n `Property '${propertyKey}' expects an object but received ${typeof value}`,\n );\n }\n return this.parse(\n typeConstructor as new (data: any) => object,\n value as Record<string, unknown>,\n );\n }\n\n throw new Error(\n `Property '${propertyKey}' 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"],"names":["ENTITY_METADATA_KEY","PROPERTY_METADATA_KEY","PROPERTY_OPTIONS_METADATA_KEY","isEqualWith","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","data","isOptional","optional","deserializeValue","typeConstructor","type","array","isSparse","sparse","index","deserialize","deserializeSingleValue","String","Number","Boolean","BigInt","date","isNaN","getTime"],"mappings":"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"}
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} from './types.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 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 * - Problems are accessible via EntityUtils.problems()\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 = EntityUtils.parse(User, json);\n * const userStrict = EntityUtils.parse(User, json, { strict: true });\n * ```\n */\n static parse<T extends object>(\n entityClass: new (data: any) => T,\n plainObject: Record<string, unknown>,\n options?: { strict?: boolean },\n ): T {\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 if (propertyOptions.passthrough === true) {\n const value = plainObject[key];\n data[key] = value;\n continue;\n }\n\n const value = plainObject[key];\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] = 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 const instance = new entityClass(data);\n\n rawInputStorage.set(instance, plainObject);\n\n const problems = this.validate(instance);\n\n if (problems.length > 0) {\n if (strict) {\n throw new ValidationError(problems);\n } else {\n problemsStorage.set(instance, problems);\n }\n }\n\n return instance;\n }\n\n /**\n * Deserializes a single value according to the type metadata\n * @private\n */\n private static deserializeValue(\n value: unknown,\n options: PropertyOptions,\n ): 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(this.deserializeSingleValue(item, typeConstructor));\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 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 deserializeSingleValue(\n value: unknown,\n typeConstructor: any,\n ): 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 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 validatePropertyValue(\n propertyPath: string,\n value: unknown,\n validators: PropertyOptions['validators'],\n ): Problem[] {\n const problems: Problem[] = [];\n\n if (validators) {\n for (const validator of validators) {\n const validatorProblems = 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 nestedProblems = EntityUtils.validate(value);\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 runPropertyValidators(\n key: string,\n value: unknown,\n options: PropertyOptions,\n ): 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 = 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 = 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 = this.validatePropertyValue(\n elementPath,\n element,\n validators,\n );\n problems.push(...elementProblems);\n }\n }\n }\n }\n\n return problems;\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 Array of Problems found during validation (empty if valid)\n *\n * @remarks\n * - Property validators run first, then entity validators\n * - Each validator returns an array of Problems\n * - Empty array means no problems found\n *\n * @example\n * ```typescript\n * const user = new User({ name: '', age: -5 });\n * const problems = EntityUtils.validate(user);\n * console.log(problems); // [Problem, Problem, ...]\n * ```\n */\n static validate<T extends object>(instance: T): 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 = 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 = (instance as any)[validatorMethod]();\n if (Array.isArray(validatorProblems)) {\n problems.push(...validatorProblems);\n }\n }\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.problems(user);\n * console.log(problems); // [Problem, ...]\n * ```\n */\n static problems<T extends object>(instance: T): Problem[] {\n return problemsStorage.get(instance) || [];\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 * 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","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","instance","set","validate","typeConstructor","type","array","isSparse","sparse","arrayProblems","index","deserialize","deserializeSingleValue","validatePropertyValue","propertyPath","validators","validator","validatorProblems","problem","nestedProblems","prependedProblems","runPropertyValidators","isPassthrough","valueProblems","arrayValidators","i","element","elementPath","elementProblems","validationProblems","entityValidators","getEntityValidators","validatorMethod","get","getRawInput","protoValidators"],"mappings":"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"}
@@ -11,4 +11,30 @@
11
11
  * ```
12
12
  */
13
13
  export declare function Entity(): ClassDecorator;
14
+ /**
15
+ * Decorator that marks a method as an entity validator.
16
+ * The method should return an array of Problems.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * @Entity()
21
+ * class User {
22
+ * @Property({ type: () => String }) firstName!: string;
23
+ * @Property({ type: () => String }) lastName!: string;
24
+ *
25
+ * @EntityValidator()
26
+ * validateNames(): Problem[] {
27
+ * const problems: Problem[] = [];
28
+ * if (this.firstName === this.lastName) {
29
+ * problems.push(new Problem({
30
+ * property: 'firstName',
31
+ * message: 'First and last name cannot be the same'
32
+ * }));
33
+ * }
34
+ * return problems;
35
+ * }
36
+ * }
37
+ * ```
38
+ */
39
+ export declare function EntityValidator(): MethodDecorator;
14
40
  //# sourceMappingURL=entity.d.ts.map
@@ -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"}
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,4 +1,4 @@
1
- import { ENTITY_METADATA_KEY } from './types.js';
1
+ import { ENTITY_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.
@@ -17,5 +17,41 @@ import { ENTITY_METADATA_KEY } from './types.js';
17
17
  Reflect.defineMetadata(ENTITY_METADATA_KEY, true, target);
18
18
  };
19
19
  }
20
+ /**
21
+ * Decorator that marks a method as an entity validator.
22
+ * The method should return an array of Problems.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * @Entity()
27
+ * class User {
28
+ * @Property({ type: () => String }) firstName!: string;
29
+ * @Property({ type: () => String }) lastName!: string;
30
+ *
31
+ * @EntityValidator()
32
+ * validateNames(): Problem[] {
33
+ * const problems: Problem[] = [];
34
+ * if (this.firstName === this.lastName) {
35
+ * problems.push(new Problem({
36
+ * property: 'firstName',
37
+ * message: 'First and last name cannot be the same'
38
+ * }));
39
+ * }
40
+ * return problems;
41
+ * }
42
+ * }
43
+ * ```
44
+ */ export function EntityValidator() {
45
+ return (target, propertyKey)=>{
46
+ if (typeof propertyKey !== 'string') {
47
+ return;
48
+ }
49
+ const existingValidators = Reflect.getOwnMetadata(ENTITY_VALIDATOR_METADATA_KEY, target) || [];
50
+ if (!existingValidators.includes(propertyKey)) {
51
+ existingValidators.push(propertyKey);
52
+ }
53
+ Reflect.defineMetadata(ENTITY_VALIDATOR_METADATA_KEY, existingValidators, target);
54
+ };
55
+ }
20
56
 
21
57
  //# sourceMappingURL=entity.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/entity.ts"],"sourcesContent":["import { ENTITY_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"],"names":["ENTITY_METADATA_KEY","Entity","target","Reflect","defineMetadata"],"mappings":"AAAA,SAASA,mBAAmB,QAAQ,aAAa;AAEjD;;;;;;;;;;;CAWC,GACD,OAAO,SAASC;IACd,sEAAsE;IACtE,OAAO,SAAUC,MAAgB;QAC/B,0CAA0C;QAC1CC,QAAQC,cAAc,CAACJ,qBAAqB,MAAME;IACpD;AACF"}
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"}
@@ -0,0 +1,15 @@
1
+ import type { PrimitiveConstructor } from './types.js';
2
+ /**
3
+ * Checks if a constructor is a supported primitive type constructor
4
+ */
5
+ export declare function isPrimitiveConstructor(constructor: any): constructor is PrimitiveConstructor;
6
+ /**
7
+ * Deserializes a primitive value based on its type constructor
8
+ *
9
+ * @param value - The value to deserialize
10
+ * @param typeConstructor - The primitive type constructor (String, Number, Boolean, BigInt, Date)
11
+ * @returns The deserialized value
12
+ * @throws ValidationError with empty property if type mismatch or parse error
13
+ */
14
+ export declare function deserializePrimitive(value: unknown, typeConstructor: PrimitiveConstructor): string | number | boolean | bigint | Date;
15
+ //# sourceMappingURL=primitive-deserializers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitive-deserializers.d.ts","sourceRoot":"","sources":["../../src/lib/primitive-deserializers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,GAAG,GACf,WAAW,IAAI,oBAAoB,CAQrC;AA4ED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,EACd,eAAe,EAAE,oBAAoB,GACpC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,CAkB3C"}
@@ -0,0 +1,87 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */ import { createValidationError } from './validation-utils.js';
2
+ /**
3
+ * Checks if a constructor is a supported primitive type constructor
4
+ */ export function isPrimitiveConstructor(constructor) {
5
+ return constructor === String || constructor === Number || constructor === Boolean || constructor === BigInt || constructor === Date;
6
+ }
7
+ /**
8
+ * Deserializes a string value
9
+ */ function deserializeString(value) {
10
+ if (typeof value !== 'string') {
11
+ throw createValidationError(`Expects a string but received ${typeof value}`);
12
+ }
13
+ return value;
14
+ }
15
+ /**
16
+ * Deserializes a number value
17
+ */ function deserializeNumber(value) {
18
+ if (typeof value !== 'number') {
19
+ throw createValidationError(`Expects a number but received ${typeof value}`);
20
+ }
21
+ return value;
22
+ }
23
+ /**
24
+ * Deserializes a boolean value
25
+ */ function deserializeBoolean(value) {
26
+ if (typeof value !== 'boolean') {
27
+ throw createValidationError(`Expects a boolean but received ${typeof value}`);
28
+ }
29
+ return value;
30
+ }
31
+ /**
32
+ * Deserializes a BigInt value (accepts bigint or string)
33
+ */ function deserializeBigInt(value) {
34
+ if (typeof value === 'bigint') {
35
+ return value;
36
+ }
37
+ if (typeof value === 'string') {
38
+ try {
39
+ return BigInt(value);
40
+ } catch {
41
+ throw createValidationError(`Cannot parse '${value}' as BigInt`);
42
+ }
43
+ }
44
+ throw createValidationError(`Expects a bigint or string but received ${typeof value}`);
45
+ }
46
+ /**
47
+ * Deserializes a Date value (accepts Date instance or ISO string)
48
+ */ function deserializeDate(value) {
49
+ if (value instanceof Date) {
50
+ return value;
51
+ }
52
+ if (typeof value === 'string') {
53
+ const date = new Date(value);
54
+ if (isNaN(date.getTime())) {
55
+ throw createValidationError(`Cannot parse '${value}' as Date`);
56
+ }
57
+ return date;
58
+ }
59
+ throw createValidationError(`Expects a Date or ISO string but received ${typeof value}`);
60
+ }
61
+ /**
62
+ * Deserializes a primitive value based on its type constructor
63
+ *
64
+ * @param value - The value to deserialize
65
+ * @param typeConstructor - The primitive type constructor (String, Number, Boolean, BigInt, Date)
66
+ * @returns The deserialized value
67
+ * @throws ValidationError with empty property if type mismatch or parse error
68
+ */ export function deserializePrimitive(value, typeConstructor) {
69
+ if (typeConstructor === String) {
70
+ return deserializeString(value);
71
+ }
72
+ if (typeConstructor === Number) {
73
+ return deserializeNumber(value);
74
+ }
75
+ if (typeConstructor === Boolean) {
76
+ return deserializeBoolean(value);
77
+ }
78
+ if (typeConstructor === BigInt) {
79
+ return deserializeBigInt(value);
80
+ }
81
+ if (typeConstructor === Date) {
82
+ return deserializeDate(value);
83
+ }
84
+ throw createValidationError(`Unknown primitive type constructor`);
85
+ }
86
+
87
+ //# sourceMappingURL=primitive-deserializers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/primitive-deserializers.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { PrimitiveConstructor } from './types.js';\nimport { createValidationError } from './validation-utils.js';\n\n/**\n * Checks if a constructor is a supported primitive type constructor\n */\nexport function isPrimitiveConstructor(\n constructor: any,\n): constructor is PrimitiveConstructor {\n return (\n constructor === String ||\n constructor === Number ||\n constructor === Boolean ||\n constructor === BigInt ||\n constructor === Date\n );\n}\n\n/**\n * Deserializes a string value\n */\nfunction deserializeString(value: unknown): string {\n if (typeof value !== 'string') {\n throw createValidationError(\n `Expects a string but received ${typeof value}`,\n );\n }\n return value;\n}\n\n/**\n * Deserializes a number value\n */\nfunction deserializeNumber(value: unknown): number {\n if (typeof value !== 'number') {\n throw createValidationError(\n `Expects a number but received ${typeof value}`,\n );\n }\n return value;\n}\n\n/**\n * Deserializes a boolean value\n */\nfunction deserializeBoolean(value: unknown): boolean {\n if (typeof value !== 'boolean') {\n throw createValidationError(\n `Expects a boolean but received ${typeof value}`,\n );\n }\n return value;\n}\n\n/**\n * Deserializes a BigInt value (accepts bigint or string)\n */\nfunction deserializeBigInt(value: unknown): bigint {\n if (typeof value === 'bigint') {\n return value;\n }\n if (typeof value === 'string') {\n try {\n return BigInt(value);\n } catch {\n throw createValidationError(`Cannot parse '${value}' as BigInt`);\n }\n }\n throw createValidationError(\n `Expects a bigint or string but received ${typeof value}`,\n );\n}\n\n/**\n * Deserializes a Date value (accepts Date instance or ISO string)\n */\nfunction deserializeDate(value: unknown): Date {\n if (value instanceof Date) {\n return value;\n }\n if (typeof value === 'string') {\n const date = new Date(value);\n if (isNaN(date.getTime())) {\n throw createValidationError(`Cannot parse '${value}' as Date`);\n }\n return date;\n }\n throw createValidationError(\n `Expects a Date or ISO string but received ${typeof value}`,\n );\n}\n\n/**\n * Deserializes a primitive value based on its type constructor\n *\n * @param value - The value to deserialize\n * @param typeConstructor - The primitive type constructor (String, Number, Boolean, BigInt, Date)\n * @returns The deserialized value\n * @throws ValidationError with empty property if type mismatch or parse error\n */\nexport function deserializePrimitive(\n value: unknown,\n typeConstructor: PrimitiveConstructor,\n): string | number | boolean | bigint | Date {\n if (typeConstructor === String) {\n return deserializeString(value);\n }\n if (typeConstructor === Number) {\n return deserializeNumber(value);\n }\n if (typeConstructor === Boolean) {\n return deserializeBoolean(value);\n }\n if (typeConstructor === BigInt) {\n return deserializeBigInt(value);\n }\n if (typeConstructor === Date) {\n return deserializeDate(value);\n }\n\n throw createValidationError(`Unknown primitive type constructor`);\n}\n"],"names":["createValidationError","isPrimitiveConstructor","constructor","String","Number","Boolean","BigInt","Date","deserializeString","value","deserializeNumber","deserializeBoolean","deserializeBigInt","deserializeDate","date","isNaN","getTime","deserializePrimitive","typeConstructor"],"mappings":"AAAA,qDAAqD,GAErD,SAASA,qBAAqB,QAAQ,wBAAwB;AAE9D;;CAEC,GACD,OAAO,SAASC,uBACdC,WAAgB;IAEhB,OACEA,gBAAgBC,UAChBD,gBAAgBE,UAChBF,gBAAgBG,WAChBH,gBAAgBI,UAChBJ,gBAAgBK;AAEpB;AAEA;;CAEC,GACD,SAASC,kBAAkBC,KAAc;IACvC,IAAI,OAAOA,UAAU,UAAU;QAC7B,MAAMT,sBACJ,CAAC,8BAA8B,EAAE,OAAOS,OAAO;IAEnD;IACA,OAAOA;AACT;AAEA;;CAEC,GACD,SAASC,kBAAkBD,KAAc;IACvC,IAAI,OAAOA,UAAU,UAAU;QAC7B,MAAMT,sBACJ,CAAC,8BAA8B,EAAE,OAAOS,OAAO;IAEnD;IACA,OAAOA;AACT;AAEA;;CAEC,GACD,SAASE,mBAAmBF,KAAc;IACxC,IAAI,OAAOA,UAAU,WAAW;QAC9B,MAAMT,sBACJ,CAAC,+BAA+B,EAAE,OAAOS,OAAO;IAEpD;IACA,OAAOA;AACT;AAEA;;CAEC,GACD,SAASG,kBAAkBH,KAAc;IACvC,IAAI,OAAOA,UAAU,UAAU;QAC7B,OAAOA;IACT;IACA,IAAI,OAAOA,UAAU,UAAU;QAC7B,IAAI;YACF,OAAOH,OAAOG;QAChB,EAAE,OAAM;YACN,MAAMT,sBAAsB,CAAC,cAAc,EAAES,MAAM,WAAW,CAAC;QACjE;IACF;IACA,MAAMT,sBACJ,CAAC,wCAAwC,EAAE,OAAOS,OAAO;AAE7D;AAEA;;CAEC,GACD,SAASI,gBAAgBJ,KAAc;IACrC,IAAIA,iBAAiBF,MAAM;QACzB,OAAOE;IACT;IACA,IAAI,OAAOA,UAAU,UAAU;QAC7B,MAAMK,OAAO,IAAIP,KAAKE;QACtB,IAAIM,MAAMD,KAAKE,OAAO,KAAK;YACzB,MAAMhB,sBAAsB,CAAC,cAAc,EAAES,MAAM,SAAS,CAAC;QAC/D;QACA,OAAOK;IACT;IACA,MAAMd,sBACJ,CAAC,0CAA0C,EAAE,OAAOS,OAAO;AAE/D;AAEA;;;;;;;CAOC,GACD,OAAO,SAASQ,qBACdR,KAAc,EACdS,eAAqC;IAErC,IAAIA,oBAAoBf,QAAQ;QAC9B,OAAOK,kBAAkBC;IAC3B;IACA,IAAIS,oBAAoBd,QAAQ;QAC9B,OAAOM,kBAAkBD;IAC3B;IACA,IAAIS,oBAAoBb,SAAS;QAC/B,OAAOM,mBAAmBF;IAC5B;IACA,IAAIS,oBAAoBZ,QAAQ;QAC9B,OAAOM,kBAAkBH;IAC3B;IACA,IAAIS,oBAAoBX,MAAM;QAC5B,OAAOM,gBAAgBJ;IACzB;IAEA,MAAMT,sBAAsB,CAAC,kCAAkC,CAAC;AAClE"}
@@ -0,0 +1,14 @@
1
+ import 'reflect-metadata';
2
+ /**
3
+ * Represents a SOFT validation problem
4
+ */
5
+ export declare class Problem {
6
+ readonly property: string;
7
+ readonly message: string;
8
+ constructor(data: {
9
+ property: string;
10
+ message: string;
11
+ });
12
+ toString(): string;
13
+ }
14
+ //# sourceMappingURL=problem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"problem.d.ts","sourceRoot":"","sources":["../../src/lib/problem.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAI1B;;GAEG;AACH,qBACa,OAAO;IAElB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAG1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAKvD,QAAQ,IAAI,MAAM;CAGnB"}
@@ -0,0 +1,31 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { _ as _ts_metadata } from "@swc/helpers/_/_ts_metadata";
3
+ import 'reflect-metadata';
4
+ import { Entity } from './entity.js';
5
+ import { StringProperty } from './property.js';
6
+ export class Problem {
7
+ constructor(data){
8
+ this.property = data.property;
9
+ this.message = data.message;
10
+ }
11
+ toString() {
12
+ return `${this.property}: ${this.message}`;
13
+ }
14
+ }
15
+ _ts_decorate([
16
+ StringProperty(),
17
+ _ts_metadata("design:type", String)
18
+ ], Problem.prototype, "property", void 0);
19
+ _ts_decorate([
20
+ StringProperty(),
21
+ _ts_metadata("design:type", String)
22
+ ], Problem.prototype, "message", void 0);
23
+ Problem = _ts_decorate([
24
+ Entity(),
25
+ _ts_metadata("design:type", Function),
26
+ _ts_metadata("design:paramtypes", [
27
+ Object
28
+ ])
29
+ ], Problem);
30
+
31
+ //# sourceMappingURL=problem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/problem.ts"],"sourcesContent":["import 'reflect-metadata';\nimport { Entity } from './entity.js';\nimport { StringProperty } from './property.js';\n\n/**\n * Represents a SOFT validation problem\n */\n@Entity()\nexport class Problem {\n @StringProperty()\n readonly property: string;\n\n @StringProperty()\n readonly message: string;\n\n constructor(data: { property: string; message: string }) {\n this.property = data.property;\n this.message = data.message;\n }\n\n toString(): string {\n return `${this.property}: ${this.message}`;\n }\n}\n"],"names":["Entity","StringProperty","Problem","data","property","message","toString"],"mappings":";;AAAA,OAAO,mBAAmB;AAC1B,SAASA,MAAM,QAAQ,cAAc;AACrC,SAASC,cAAc,QAAQ,gBAAgB;AAM/C,OAAO,MAAMC;IAOX,YAAYC,IAA2C,CAAE;QACvD,IAAI,CAACC,QAAQ,GAAGD,KAAKC,QAAQ;QAC7B,IAAI,CAACC,OAAO,GAAGF,KAAKE,OAAO;IAC7B;IAEAC,WAAmB;QACjB,OAAO,GAAG,IAAI,CAACF,QAAQ,CAAC,EAAE,EAAE,IAAI,CAACC,OAAO,EAAE;IAC5C;AACF"}
@@ -123,4 +123,16 @@ export declare function ArrayProperty<T, C extends CtorLike<T>>(type: () => C, o
123
123
  * }
124
124
  */
125
125
  export declare function PassthroughProperty(): PropertyDecorator;
126
+ export declare const StringifiableProperty: <T extends {
127
+ equals?(other: T): boolean;
128
+ toString(): string;
129
+ }, C extends CtorLike<T> & {
130
+ parse(value: string): T;
131
+ }>(type: () => C, data?: Omit<PropertyOptions<T, C>, "serialize" | "deserialize" | "passthrough" | "type" | "equals">) => PropertyDecorator;
132
+ export declare const SerializableProperty: <T extends {
133
+ equals?(other: T): boolean;
134
+ toJSON(): unknown;
135
+ }, C extends CtorLike<T> & {
136
+ parse(value: unknown): T;
137
+ }>(type: () => C, data?: Omit<PropertyOptions<T, C>, "serialize" | "deserialize" | "passthrough" | "type" | "equals">) => PropertyDecorator;
126
138
  //# sourceMappingURL=property.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"property.d.ts","sourceRoot":"","sources":["../../src/lib/property.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,OAAO,EACP,KAAK,QAAQ,EAGb,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAC/C,OAAO,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC7B,iBAAiB,CAqEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,GACjE,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,GACjE,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,kBAAkB,CAAC,EAAE,MAAM,CAAC,GACnE,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,GAC7D,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,GACjE,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EACD,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG;IAAE,KAAK,IAAI,EAAE,GAAG,GAAG,CAAC,CAAA;CAAE,EAE7C,IAAI,EAAE,MAAM,CAAC,EACb,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,GAC5C,iBAAiB,CAEnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EACpD,IAAI,EAAE,MAAM,CAAC,EACb,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GACtD,iBAAiB,CAEnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,IAAI,iBAAiB,CAGvD"}
1
+ {"version":3,"file":"property.d.ts","sourceRoot":"","sources":["../../src/lib/property.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,OAAO,EACP,KAAK,QAAQ,EAIb,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAC/C,OAAO,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC7B,iBAAiB,CA2EnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,GACjE,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,GACjE,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,kBAAkB,CAAC,EAAE,MAAM,CAAC,GACnE,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,GAC7D,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,GACjE,iBAAiB,CAEnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EACD,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG;IAAE,KAAK,IAAI,EAAE,GAAG,GAAG,CAAC,CAAA;CAAE,EAE7C,IAAI,EAAE,MAAM,CAAC,EACb,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,GAC5C,iBAAiB,CAEnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EACpD,IAAI,EAAE,MAAM,CAAC,EACb,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GACtD,iBAAiB,CAEnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,IAAI,iBAAiB,CAGvD;AAED,eAAO,MAAM,qBAAqB,GAChC,CAAC,SAAS;IAAE,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;IAAC,QAAQ,IAAI,MAAM,CAAA;CAAE,EAC5D,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAA;CAAE,EAEnD,MAAM,MAAM,CAAC,EACb,OAAM,IAAI,CACR,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EACrB,WAAW,GAAG,aAAa,GAAG,aAAa,GAAG,MAAM,GAAG,QAAQ,CAC3D,KACL,iBAYC,CAAC;AAEL,eAAO,MAAM,oBAAoB,GAC/B,CAAC,SAAS;IAAE,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;IAAC,MAAM,IAAI,OAAO,CAAA;CAAE,EAC3D,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,CAAA;CAAE,EAEpD,MAAM,MAAM,CAAC,EACb,OAAM,IAAI,CACR,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EACrB,WAAW,GAAG,aAAa,GAAG,aAAa,GAAG,MAAM,GAAG,QAAQ,CAC3D,sBAWJ,CAAC"}
@@ -1,4 +1,5 @@
1
- /* eslint-disable @typescript-eslint/no-wrapper-object-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { PROPERTY_METADATA_KEY, PROPERTY_OPTIONS_METADATA_KEY } from './types.js';
1
+ /* eslint-disable @typescript-eslint/no-wrapper-object-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { isEqual } from 'lodash-es';
2
+ import { PROPERTY_METADATA_KEY, PROPERTY_OPTIONS_METADATA_KEY } from './types.js';
2
3
  /**
3
4
  * Property decorator that marks class properties with metadata.
4
5
  * This decorator can be used to identify and track properties within classes.
@@ -43,6 +44,9 @@
43
44
  if (options.sparse === true && options.array !== true) {
44
45
  throw new Error(`Property '${propertyKey}' has sparse: true but array is not true. The sparse option only applies to arrays.`);
45
46
  }
47
+ if (options.arrayValidators && options.array !== true) {
48
+ throw new Error(`Property '${propertyKey}' has arrayValidators defined but array is not true. The arrayValidators option only applies to arrays.`);
49
+ }
46
50
  // Validate serialize/deserialize pairing
47
51
  const hasSerialize = options.serialize !== undefined;
48
52
  const hasDeserialize = options.deserialize !== undefined;
@@ -191,5 +195,26 @@
191
195
  passthrough: true
192
196
  });
193
197
  }
198
+ export const StringifiableProperty = (type, data = {})=>Property({
199
+ ...data,
200
+ type,
201
+ equals: (a, b)=>a.equals ? a.equals(b) : a.toString() === b.toString(),
202
+ serialize: (value)=>value.toString(),
203
+ deserialize: (value)=>{
204
+ if (typeof value === 'string') {
205
+ return type().parse(value);
206
+ }
207
+ throw new Error(`Invalid value ${type().name}: ${String(value)}`);
208
+ }
209
+ });
210
+ export const SerializableProperty = (type, data = {})=>Property({
211
+ ...data,
212
+ type,
213
+ equals: (a, b)=>a.equals ? a.equals(b) : isEqual(a.toJSON(), b.toJSON()),
214
+ serialize: (value)=>value.toJSON(),
215
+ deserialize: (value)=>{
216
+ return type().parse(value);
217
+ }
218
+ });
194
219
 
195
220
  //# sourceMappingURL=property.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/property.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-wrapper-object-types */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n AnyCtor,\n type CtorLike,\n PROPERTY_METADATA_KEY,\n PROPERTY_OPTIONS_METADATA_KEY,\n PropertyOptions,\n} from './types.js';\n\n/**\n * Property decorator that marks class properties with metadata.\n * This decorator can be used to identify and track properties within classes.\n *\n * @param options - Configuration for the property (type is required)\n *\n * @example\n * class User {\n * @Property({ type: () => String })\n * name: string;\n *\n * @Property({ type: () => String, equals: (a, b) => a.toLowerCase() === b.toLowerCase() })\n * email: string;\n *\n * @Property({ type: () => Number })\n * age: number;\n * }\n */\nexport function Property<T, C extends CtorLike<T>>(\n options: PropertyOptions<T, C>,\n): PropertyDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n if (typeof propertyKey !== 'string') {\n return;\n }\n\n const existingProperties: string[] =\n Reflect.getOwnMetadata(PROPERTY_METADATA_KEY, target) || [];\n\n if (!existingProperties.includes(propertyKey)) {\n existingProperties.push(propertyKey);\n }\n\n Reflect.defineMetadata(PROPERTY_METADATA_KEY, existingProperties, target);\n\n if (options.passthrough === true) {\n if (options.array === true) {\n throw new Error(\n `Property '${propertyKey}' has passthrough: true and array: true. Passthrough cannot be combined with array.`,\n );\n }\n if (options.optional === true) {\n throw new Error(\n `Property '${propertyKey}' has passthrough: true and optional: true. Passthrough cannot be combined with optional.`,\n );\n }\n if (options.sparse === true) {\n throw new Error(\n `Property '${propertyKey}' has passthrough: true and sparse: true. Passthrough cannot be combined with sparse.`,\n );\n }\n if (\n options.serialize !== undefined ||\n options.deserialize !== undefined\n ) {\n throw new Error(\n `Property '${propertyKey}' has passthrough: true and custom serialize/deserialize functions. Passthrough cannot be combined with serialize or deserialize.`,\n );\n }\n }\n\n if (options.sparse === true && options.array !== true) {\n throw new Error(\n `Property '${propertyKey}' has sparse: true but array is not true. The sparse option only applies to arrays.`,\n );\n }\n\n // Validate serialize/deserialize pairing\n const hasSerialize = options.serialize !== undefined;\n const hasDeserialize = options.deserialize !== undefined;\n if (hasSerialize !== hasDeserialize) {\n throw new Error(\n `Property '${propertyKey}' must define both serialize and deserialize functions, or neither. Found only ${hasSerialize ? 'serialize' : 'deserialize'}.`,\n );\n }\n\n const existingOptions: Record<\n string,\n PropertyOptions<any, any>\n > = Reflect.getOwnMetadata(PROPERTY_OPTIONS_METADATA_KEY, target) || {};\n\n existingOptions[propertyKey] = options;\n\n Reflect.defineMetadata(\n PROPERTY_OPTIONS_METADATA_KEY,\n existingOptions,\n target,\n );\n };\n}\n\n/**\n * Helper decorator for string properties\n * @example\n * class User {\n * @StringProperty()\n * name!: string;\n *\n * @StringProperty({ optional: true })\n * nickname?: string;\n * }\n */\nexport function StringProperty(\n options?: Omit<PropertyOptions<string, StringConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => String });\n}\n\n/**\n * Helper decorator for number properties\n * @example\n * class User {\n * @NumberProperty()\n * age!: number;\n *\n * @NumberProperty({ optional: true })\n * score?: number;\n * }\n */\nexport function NumberProperty(\n options?: Omit<PropertyOptions<number, NumberConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => Number });\n}\n\n/**\n * Helper decorator for boolean properties\n * @example\n * class User {\n * @BooleanProperty()\n * active!: boolean;\n *\n * @BooleanProperty({ optional: true })\n * verified?: boolean;\n * }\n */\nexport function BooleanProperty(\n options?: Omit<PropertyOptions<boolean, BooleanConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => Boolean });\n}\n\n/**\n * Helper decorator for Date properties\n * @example\n * class User {\n * @DateProperty()\n * createdAt!: Date;\n *\n * @DateProperty({ optional: true })\n * deletedAt?: Date;\n * }\n */\nexport function DateProperty(\n options?: Omit<PropertyOptions<Date, DateConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => Date });\n}\n\n/**\n * Helper decorator for BigInt properties\n * @example\n * class User {\n * @BigIntProperty()\n * id!: bigint;\n *\n * @BigIntProperty({ optional: true })\n * balance?: bigint;\n * }\n */\nexport function BigIntProperty(\n options?: Omit<PropertyOptions<bigint, BigIntConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => BigInt });\n}\n\n/**\n * Helper decorator for entity properties\n * @example\n * class User {\n * @EntityProperty(() => Address)\n * address!: Address;\n *\n * @EntityProperty(() => Profile, { optional: true })\n * profile?: Profile;\n * }\n */\nexport function EntityProperty<\n T,\n C extends AnyCtor<T> & { new (data: any): T },\n>(\n type: () => C,\n options?: Omit<PropertyOptions<T, C>, 'type'>,\n): PropertyDecorator {\n return Property<T, C>({ ...options, type });\n}\n\n/**\n * Helper decorator for array properties\n * @example\n * class User {\n * @ArrayProperty(() => String)\n * tags!: string[];\n *\n * @ArrayProperty(() => Phone)\n * phones!: Phone[];\n *\n * @ArrayProperty(() => Number, { optional: true })\n * scores?: number[];\n *\n * @ArrayProperty(() => String, { sparse: true })\n * sparseList!: (string | null)[];\n * }\n */\nexport function ArrayProperty<T, C extends CtorLike<T>>(\n type: () => C,\n options?: Omit<PropertyOptions<T, C>, 'type' | 'array'>,\n): PropertyDecorator {\n return Property({ ...options, type, array: true });\n}\n\n/**\n * Helper decorator for passthrough properties that bypass type validation.\n * Use this for generic types like Record<string, unknown>, any, or custom objects.\n * @example\n * class Config {\n * @PassthroughProperty()\n * metadata!: Record<string, unknown>;\n *\n * @PassthroughProperty()\n * customData!: any;\n * }\n */\nexport function PassthroughProperty(): PropertyDecorator {\n // Use a dummy type since type is mandatory but not used with passthrough\n return Property({ type: () => Object, passthrough: true });\n}\n"],"names":["PROPERTY_METADATA_KEY","PROPERTY_OPTIONS_METADATA_KEY","Property","options","target","propertyKey","existingProperties","Reflect","getOwnMetadata","includes","push","defineMetadata","passthrough","array","Error","optional","sparse","serialize","undefined","deserialize","hasSerialize","hasDeserialize","existingOptions","StringProperty","type","String","NumberProperty","Number","BooleanProperty","Boolean","DateProperty","Date","BigIntProperty","BigInt","EntityProperty","ArrayProperty","PassthroughProperty","Object"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/lib/property.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-wrapper-object-types */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { isEqual } from 'lodash-es';\nimport {\n AnyCtor,\n type CtorLike,\n type InstanceOfCtorLike,\n PROPERTY_METADATA_KEY,\n PROPERTY_OPTIONS_METADATA_KEY,\n PropertyOptions,\n} from './types.js';\n\n/**\n * Property decorator that marks class properties with metadata.\n * This decorator can be used to identify and track properties within classes.\n *\n * @param options - Configuration for the property (type is required)\n *\n * @example\n * class User {\n * @Property({ type: () => String })\n * name: string;\n *\n * @Property({ type: () => String, equals: (a, b) => a.toLowerCase() === b.toLowerCase() })\n * email: string;\n *\n * @Property({ type: () => Number })\n * age: number;\n * }\n */\nexport function Property<T, C extends CtorLike<T>>(\n options: PropertyOptions<T, C>,\n): PropertyDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n if (typeof propertyKey !== 'string') {\n return;\n }\n\n const existingProperties: string[] =\n Reflect.getOwnMetadata(PROPERTY_METADATA_KEY, target) || [];\n\n if (!existingProperties.includes(propertyKey)) {\n existingProperties.push(propertyKey);\n }\n\n Reflect.defineMetadata(PROPERTY_METADATA_KEY, existingProperties, target);\n\n if (options.passthrough === true) {\n if (options.array === true) {\n throw new Error(\n `Property '${propertyKey}' has passthrough: true and array: true. Passthrough cannot be combined with array.`,\n );\n }\n if (options.optional === true) {\n throw new Error(\n `Property '${propertyKey}' has passthrough: true and optional: true. Passthrough cannot be combined with optional.`,\n );\n }\n if (options.sparse === true) {\n throw new Error(\n `Property '${propertyKey}' has passthrough: true and sparse: true. Passthrough cannot be combined with sparse.`,\n );\n }\n if (\n options.serialize !== undefined ||\n options.deserialize !== undefined\n ) {\n throw new Error(\n `Property '${propertyKey}' has passthrough: true and custom serialize/deserialize functions. Passthrough cannot be combined with serialize or deserialize.`,\n );\n }\n }\n\n if (options.sparse === true && options.array !== true) {\n throw new Error(\n `Property '${propertyKey}' has sparse: true but array is not true. The sparse option only applies to arrays.`,\n );\n }\n\n if (options.arrayValidators && options.array !== true) {\n throw new Error(\n `Property '${propertyKey}' has arrayValidators defined but array is not true. The arrayValidators option only applies to arrays.`,\n );\n }\n\n // Validate serialize/deserialize pairing\n const hasSerialize = options.serialize !== undefined;\n const hasDeserialize = options.deserialize !== undefined;\n if (hasSerialize !== hasDeserialize) {\n throw new Error(\n `Property '${propertyKey}' must define both serialize and deserialize functions, or neither. Found only ${hasSerialize ? 'serialize' : 'deserialize'}.`,\n );\n }\n\n const existingOptions: Record<\n string,\n PropertyOptions<any, any>\n > = Reflect.getOwnMetadata(PROPERTY_OPTIONS_METADATA_KEY, target) || {};\n\n existingOptions[propertyKey] = options;\n\n Reflect.defineMetadata(\n PROPERTY_OPTIONS_METADATA_KEY,\n existingOptions,\n target,\n );\n };\n}\n\n/**\n * Helper decorator for string properties\n * @example\n * class User {\n * @StringProperty()\n * name!: string;\n *\n * @StringProperty({ optional: true })\n * nickname?: string;\n * }\n */\nexport function StringProperty(\n options?: Omit<PropertyOptions<string, StringConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => String });\n}\n\n/**\n * Helper decorator for number properties\n * @example\n * class User {\n * @NumberProperty()\n * age!: number;\n *\n * @NumberProperty({ optional: true })\n * score?: number;\n * }\n */\nexport function NumberProperty(\n options?: Omit<PropertyOptions<number, NumberConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => Number });\n}\n\n/**\n * Helper decorator for boolean properties\n * @example\n * class User {\n * @BooleanProperty()\n * active!: boolean;\n *\n * @BooleanProperty({ optional: true })\n * verified?: boolean;\n * }\n */\nexport function BooleanProperty(\n options?: Omit<PropertyOptions<boolean, BooleanConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => Boolean });\n}\n\n/**\n * Helper decorator for Date properties\n * @example\n * class User {\n * @DateProperty()\n * createdAt!: Date;\n *\n * @DateProperty({ optional: true })\n * deletedAt?: Date;\n * }\n */\nexport function DateProperty(\n options?: Omit<PropertyOptions<Date, DateConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => Date });\n}\n\n/**\n * Helper decorator for BigInt properties\n * @example\n * class User {\n * @BigIntProperty()\n * id!: bigint;\n *\n * @BigIntProperty({ optional: true })\n * balance?: bigint;\n * }\n */\nexport function BigIntProperty(\n options?: Omit<PropertyOptions<bigint, BigIntConstructor>, 'type'>,\n): PropertyDecorator {\n return Property({ ...options, type: () => BigInt });\n}\n\n/**\n * Helper decorator for entity properties\n * @example\n * class User {\n * @EntityProperty(() => Address)\n * address!: Address;\n *\n * @EntityProperty(() => Profile, { optional: true })\n * profile?: Profile;\n * }\n */\nexport function EntityProperty<\n T,\n C extends AnyCtor<T> & { new (data: any): T },\n>(\n type: () => C,\n options?: Omit<PropertyOptions<T, C>, 'type'>,\n): PropertyDecorator {\n return Property<T, C>({ ...options, type });\n}\n\n/**\n * Helper decorator for array properties\n * @example\n * class User {\n * @ArrayProperty(() => String)\n * tags!: string[];\n *\n * @ArrayProperty(() => Phone)\n * phones!: Phone[];\n *\n * @ArrayProperty(() => Number, { optional: true })\n * scores?: number[];\n *\n * @ArrayProperty(() => String, { sparse: true })\n * sparseList!: (string | null)[];\n * }\n */\nexport function ArrayProperty<T, C extends CtorLike<T>>(\n type: () => C,\n options?: Omit<PropertyOptions<T, C>, 'type' | 'array'>,\n): PropertyDecorator {\n return Property({ ...options, type, array: true });\n}\n\n/**\n * Helper decorator for passthrough properties that bypass type validation.\n * Use this for generic types like Record<string, unknown>, any, or custom objects.\n * @example\n * class Config {\n * @PassthroughProperty()\n * metadata!: Record<string, unknown>;\n *\n * @PassthroughProperty()\n * customData!: any;\n * }\n */\nexport function PassthroughProperty(): PropertyDecorator {\n // Use a dummy type since type is mandatory but not used with passthrough\n return Property({ type: () => Object, passthrough: true });\n}\n\nexport const StringifiableProperty = <\n T extends { equals?(other: T): boolean; toString(): string },\n C extends CtorLike<T> & { parse(value: string): T },\n>(\n type: () => C,\n data: Omit<\n PropertyOptions<T, C>,\n 'serialize' | 'deserialize' | 'passthrough' | 'type' | 'equals'\n > = {},\n): PropertyDecorator =>\n Property<T, C>({\n ...data,\n type,\n equals: (a, b) => (a.equals ? a.equals(b) : a.toString() === b.toString()),\n serialize: (value) => value.toString(),\n deserialize: (value) => {\n if (typeof value === 'string') {\n return type().parse(value) as InstanceOfCtorLike<C>;\n }\n throw new Error(`Invalid value ${type().name}: ${String(value)}`);\n },\n });\n\nexport const SerializableProperty = <\n T extends { equals?(other: T): boolean; toJSON(): unknown },\n C extends CtorLike<T> & { parse(value: unknown): T },\n>(\n type: () => C,\n data: Omit<\n PropertyOptions<T, C>,\n 'serialize' | 'deserialize' | 'passthrough' | 'type' | 'equals'\n > = {},\n) =>\n Property({\n ...data,\n type,\n equals: (a: T, b: T) =>\n a.equals ? a.equals(b) : isEqual(a.toJSON(), b.toJSON()),\n serialize: (value: T) => value.toJSON(),\n deserialize: (value: unknown) => {\n return type().parse(value) as InstanceOfCtorLike<C>;\n },\n });\n"],"names":["isEqual","PROPERTY_METADATA_KEY","PROPERTY_OPTIONS_METADATA_KEY","Property","options","target","propertyKey","existingProperties","Reflect","getOwnMetadata","includes","push","defineMetadata","passthrough","array","Error","optional","sparse","serialize","undefined","deserialize","arrayValidators","hasSerialize","hasDeserialize","existingOptions","StringProperty","type","String","NumberProperty","Number","BooleanProperty","Boolean","DateProperty","Date","BigIntProperty","BigInt","EntityProperty","ArrayProperty","PassthroughProperty","Object","StringifiableProperty","data","equals","a","b","toString","value","parse","name","SerializableProperty","toJSON"],"mappings":"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"}