@rtpaulino/entity 0.11.0 → 0.13.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.
package/README.md CHANGED
@@ -1 +1,327 @@
1
1
  # @rtpaulino/entity
2
+
3
+ A TypeScript entity framework with decorators for metadata-driven serialization and deserialization.
4
+
5
+ ## Features
6
+
7
+ - **Type-safe decorators** for entity properties
8
+ - **Serialization** via `EntityUtils.toJSON()`
9
+ - **Deserialization** via `EntityUtils.parse()`
10
+ - **Helper decorators** for common types (String, Number, Boolean, Date, BigInt, Entity, Array)
11
+ - **Nested entities** with automatic recursive handling
12
+ - **Optional properties** with null/undefined support
13
+ - **Sparse arrays** for arrays with null/undefined elements
14
+ - **Passthrough mode** for generic types like `Record<string, unknown>`
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @rtpaulino/entity reflect-metadata
20
+ ```
21
+
22
+ Make sure to enable decorators in your `tsconfig.json`:
23
+
24
+ ```json
25
+ {
26
+ "compilerOptions": {
27
+ "experimentalDecorators": true,
28
+ "emitDecoratorMetadata": true
29
+ }
30
+ }
31
+ ```
32
+
33
+ ## Basic Usage
34
+
35
+ ```typescript
36
+ import 'reflect-metadata';
37
+ import {
38
+ Entity,
39
+ StringProperty,
40
+ NumberProperty,
41
+ EntityUtils,
42
+ } from '@rtpaulino/entity';
43
+
44
+ @Entity()
45
+ class User {
46
+ @StringProperty()
47
+ name!: string;
48
+
49
+ @NumberProperty()
50
+ age!: number;
51
+ }
52
+
53
+ // Serialization
54
+ const user = new User();
55
+ user.name = 'Alice';
56
+ user.age = 30;
57
+
58
+ const json = EntityUtils.toJSON(user);
59
+ // { name: 'Alice', age: 30 }
60
+
61
+ // Deserialization
62
+ const parsed = EntityUtils.parse(User, json);
63
+ // parsed is a User instance with name='Alice' and age=30
64
+ ```
65
+
66
+ ## Property Decorators
67
+
68
+ ### Basic Types
69
+
70
+ ```typescript
71
+ @Entity()
72
+ class Example {
73
+ @StringProperty()
74
+ text!: string;
75
+
76
+ @NumberProperty()
77
+ count!: number;
78
+
79
+ @BooleanProperty()
80
+ active!: boolean;
81
+
82
+ @DateProperty()
83
+ createdAt!: Date;
84
+
85
+ @BigIntProperty()
86
+ largeNumber!: bigint;
87
+ }
88
+ ```
89
+
90
+ ### Optional Properties
91
+
92
+ Use `optional: true` to allow `null` or `undefined`:
93
+
94
+ ```typescript
95
+ @Entity()
96
+ class User {
97
+ @StringProperty()
98
+ name!: string;
99
+
100
+ @StringProperty({ optional: true })
101
+ email?: string | null;
102
+ }
103
+ ```
104
+
105
+ ### Nested Entities
106
+
107
+ ```typescript
108
+ @Entity()
109
+ class Address {
110
+ @StringProperty()
111
+ city!: string;
112
+
113
+ @StringProperty()
114
+ country!: string;
115
+ }
116
+
117
+ @Entity()
118
+ class User {
119
+ @StringProperty()
120
+ name!: string;
121
+
122
+ @EntityProperty(() => Address)
123
+ address!: Address;
124
+ }
125
+ ```
126
+
127
+ ### Arrays
128
+
129
+ ```typescript
130
+ @Entity()
131
+ class Team {
132
+ @ArrayProperty(() => String)
133
+ members!: string[];
134
+
135
+ @ArrayProperty(() => Number)
136
+ scores!: number[];
137
+ }
138
+ ```
139
+
140
+ ### Sparse Arrays
141
+
142
+ Use `sparse: true` to allow `null` or `undefined` elements in arrays:
143
+
144
+ ```typescript
145
+ @Entity()
146
+ class Data {
147
+ @ArrayProperty(() => String, { sparse: true })
148
+ values!: (string | null)[];
149
+ }
150
+
151
+ const data = new Data();
152
+ data.values = ['a', null, 'b', undefined, 'c'];
153
+
154
+ const json = EntityUtils.toJSON(data);
155
+ // { values: ['a', null, 'b', null, 'c'] }
156
+
157
+ const parsed = EntityUtils.parse(Data, json);
158
+ // parsed.values is ['a', null, 'b', null, 'c']
159
+ ```
160
+
161
+ ### Passthrough for Generic Types
162
+
163
+ By default, the library throws errors for unknown types to prevent accidental misuse. Use `@PassthroughProperty()` decorator or `passthrough: true` option to explicitly handle generic types like `Record<string, unknown>`, `any`, or custom objects:
164
+
165
+ ```typescript
166
+ @Entity()
167
+ class Config {
168
+ @StringProperty()
169
+ name!: string;
170
+
171
+ @PassthroughProperty()
172
+ metadata!: Record<string, unknown>;
173
+
174
+ @PassthroughProperty()
175
+ customData!: any;
176
+ }
177
+
178
+ const config = new Config();
179
+ config.name = 'MyConfig';
180
+ config.metadata = {
181
+ version: '1.0.0',
182
+ tags: ['production', 'stable'],
183
+ nested: { deeply: { nested: { value: 42 } } },
184
+ };
185
+
186
+ // Serialization - data passes through as-is
187
+ const json = EntityUtils.toJSON(config);
188
+
189
+ // Deserialization - data passes through as-is
190
+ const parsed = EntityUtils.parse(Config, json);
191
+ // parsed.metadata is exactly what was in the JSON
192
+ ```
193
+
194
+ **Important:** Passthrough cannot be combined with `array`, `optional`, or `sparse` options. Use `@PassthroughProperty()` for the cleanest API.
195
+
196
+ **Without passthrough, unknown types throw errors:**
197
+
198
+ ```typescript
199
+ @Entity()
200
+ class BadExample {
201
+ @Property({ type: () => Symbol }) // Symbol is not supported
202
+ value!: symbol;
203
+ }
204
+
205
+ // This will throw:
206
+ // "Property 'value' has unknown type constructor. Supported types are: String, Number, Boolean, Date, BigInt, and @Entity() classes."
207
+ ```
208
+
209
+ ## Advanced Patterns
210
+
211
+ ### Arrays of Entities
212
+
213
+ ```typescript
214
+ @Entity()
215
+ class Comment {
216
+ @StringProperty()
217
+ text!: string;
218
+
219
+ @DateProperty()
220
+ createdAt!: Date;
221
+ }
222
+
223
+ @Entity()
224
+ class Post {
225
+ @StringProperty()
226
+ title!: string;
227
+
228
+ @ArrayProperty(() => Comment)
229
+ comments!: Comment[];
230
+ }
231
+ ```
232
+
233
+ ### Complex Nested Structures
234
+
235
+ ```typescript
236
+ @Entity()
237
+ class Organization {
238
+ @StringProperty()
239
+ name!: string;
240
+
241
+ @ArrayProperty(() => User)
242
+ admins!: User[];
243
+
244
+ @Property({ passthrough: true })
245
+ settings!: Record<string, unknown>;
246
+ }
247
+ ```
248
+
249
+ ## API Reference
250
+
251
+ ### `EntityUtils.toJSON(entity)`
252
+
253
+ Serializes an entity to a plain JavaScript object.
254
+
255
+ - Throws error for properties with unknown types (unless `passthrough: true`)
256
+ - Handles nested entities recursively
257
+ - Converts Date to ISO string, BigInt to string
258
+ - Preserves null/undefined for optional properties
259
+
260
+ ### `EntityUtils.parse<T>(entityClass, plainObject)`
261
+
262
+ Deserializes a plain object to an entity instance.
263
+
264
+ - Validates all required properties are present
265
+ - Validates property types match metadata
266
+ - Throws error for unknown types (unless `passthrough: true`)
267
+ - Handles nested entities recursively
268
+ - Converts ISO strings back to Date, strings back to BigInt
269
+
270
+ ### Property Options
271
+
272
+ The `@Property()` decorator requires configuration options. Available options are:
273
+
274
+ - `type: () => any` - **Required.** Type constructor for the property
275
+ - `array?: boolean` - Whether the property is an array
276
+ - `optional?: boolean` - Allow null/undefined values
277
+ - `sparse?: boolean` - Allow null/undefined in array elements (requires `array: true`)
278
+ - `passthrough?: boolean` - Bypass type validation for generic types (cannot be combined with array, optional, or sparse)
279
+ - `equals?: (a, b) => boolean` - Custom equality function for this property
280
+
281
+ **Note:** Use helper decorators like `@StringProperty()`, `@PassthroughProperty()`, etc. for cleaner, more readable code. These helpers automatically provide the required `type` parameter.
282
+
283
+ ## Error Handling
284
+
285
+ The library validates configuration at decorator time and throws descriptive errors for common mistakes:
286
+
287
+ ```typescript
288
+ // Missing required property
289
+ EntityUtils.parse(User, {});
290
+ // Error: Property 'name' is required but missing from input
291
+
292
+ // Null/undefined on required property
293
+ EntityUtils.parse(User, { name: null, age: 30 });
294
+ // Error: Property 'name' cannot be null or undefined
295
+
296
+ // Sparse without array (fails at decorator time)
297
+ @Property({ type: () => String, sparse: true }) // Missing array: true
298
+ values!: string[];
299
+ // Error: Property 'values' has sparse: true but array is not true
300
+
301
+ // Passthrough combined with other options (fails at decorator time)
302
+ @Property({ type: () => Object, passthrough: true, array: true })
303
+ data!: any;
304
+ // Error: Property 'data' has passthrough: true and array: true. Passthrough cannot be combined with array
305
+
306
+ // Unknown type without passthrough
307
+ @Property({ type: () => WeakMap })
308
+ map!: WeakMap<any, any>;
309
+ // Error: Property 'map' has unknown type constructor
310
+ ```
311
+
312
+ ## TypeScript Integration
313
+
314
+ All methods are fully typed:
315
+
316
+ ```typescript
317
+ const user = EntityUtils.parse(User, json); // user is typed as User
318
+ const json = EntityUtils.toJSON(user); // json is Record<string, unknown>
319
+ ```
320
+
321
+ ## License
322
+
323
+ MIT
324
+
325
+ ```
326
+
327
+ ```
@@ -31,5 +31,106 @@ export declare class EntityUtils {
31
31
  newValue: unknown;
32
32
  }[];
33
33
  static changes<T extends object>(oldEntity: T, newEntity: T): Partial<T>;
34
+ /**
35
+ * Serializes an entity to a plain object, converting only properties decorated with @Property()
36
+ *
37
+ * @param entity - The entity instance to serialize
38
+ * @returns A plain object containing only the serialized decorated properties
39
+ *
40
+ * @remarks
41
+ * Serialization rules:
42
+ * - Only properties decorated with @Property() are included
43
+ * - If a property has a custom toJSON() method, it will be used
44
+ * - Nested entities are recursively serialized using EntityUtils.toJSON()
45
+ * - Arrays are mapped with toJSON() applied to each element
46
+ * - Date objects are serialized to ISO strings
47
+ * - bigint values are serialized to strings
48
+ * - undefined values are excluded from the output
49
+ * - null values are included in the output
50
+ * - Circular references are not supported (will cause stack overflow)
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * @Entity()
55
+ * class Address {
56
+ * @Property() street: string;
57
+ * @Property() city: string;
58
+ * }
59
+ *
60
+ * @Entity()
61
+ * class User {
62
+ * @Property() name: string;
63
+ * @Property() address: Address;
64
+ * @Property() createdAt: Date;
65
+ * undecorated: string; // Will not be serialized
66
+ * }
67
+ *
68
+ * const user = new User();
69
+ * user.name = 'John';
70
+ * user.address = new Address();
71
+ * user.address.street = '123 Main St';
72
+ * user.address.city = 'Boston';
73
+ * user.createdAt = new Date('2024-01-01');
74
+ * user.undecorated = 'ignored';
75
+ *
76
+ * const json = EntityUtils.toJSON(user);
77
+ * // {
78
+ * // name: 'John',
79
+ * // address: { street: '123 Main St', city: 'Boston' },
80
+ * // createdAt: '2024-01-01T00:00:00.000Z'
81
+ * // }
82
+ * ```
83
+ */
84
+ static toJSON<T extends object>(entity: T): Record<string, unknown>;
85
+ /**
86
+ * Serializes a single value according to the toJSON rules
87
+ * @private
88
+ */
89
+ private static serializeValue;
90
+ /**
91
+ * Deserializes a plain object to an entity instance
92
+ *
93
+ * @param entityClass - The entity class constructor. Must accept a data object parameter.
94
+ * @param plainObject - The plain object to deserialize
95
+ * @returns A new instance of the entity with deserialized values
96
+ *
97
+ * @remarks
98
+ * Deserialization rules:
99
+ * - All @Property() decorators must include type metadata for parse() to work
100
+ * - Properties without type metadata will throw an error
101
+ * - Required properties (optional !== true) must be present and not null/undefined
102
+ * - Optional properties (optional === true) can be undefined or null
103
+ * - Arrays are supported with the array: true option
104
+ * - Nested entities are recursively deserialized
105
+ * - Type conversion is strict (no coercion)
106
+ * - Entity constructors must accept a required data parameter
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * @Entity()
111
+ * class User {
112
+ * @Property({ type: () => String }) name!: string;
113
+ * @Property({ type: () => Number }) age!: number;
114
+ *
115
+ * constructor(data: Partial<User>) {
116
+ * Object.assign(this, data);
117
+ * }
118
+ * }
119
+ *
120
+ * const json = { name: 'John', age: 30 };
121
+ * const user = EntityUtils.parse(User, json);
122
+ * ```
123
+ */
124
+ static parse<T extends object>(entityClass: new (data: any) => T, plainObject: Record<string, unknown>): T;
125
+ /**
126
+ * Deserializes a single value according to the type metadata
127
+ * @private
128
+ */
129
+ private static deserializeValue;
130
+ /**
131
+ * Deserializes a single non-array value
132
+ * @private
133
+ */
134
+ private static deserializeSingleValue;
34
135
  }
35
136
  //# sourceMappingURL=entity-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entity-utils.d.ts","sourceRoot":"","sources":["../../src/lib/entity-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,eAAe,EAChB,MAAM,YAAY,CAAC;AAGpB,qBAAa,WAAW;IACtB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,MAAM;IAmB5C,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO;IAQhD,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAoChD,MAAM,CAAC,kBAAkB,CACvB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,eAAe,GAAG,SAAS;IA8B9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO;IA2B9C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,MAAM,EAC1B,SAAS,EAAE,CAAC,EACZ,SAAS,EAAE,CAAC,GACX;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,EAAE;IAoC/D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAYzE"}
1
+ {"version":3,"file":"entity-utils.d.ts","sourceRoot":"","sources":["../../src/lib/entity-utils.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,eAAe,EAChB,MAAM,YAAY,CAAC;AAGpB,qBAAa,WAAW;IACtB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,MAAM;IAmB5C,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO;IAQhD,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAoChD,MAAM,CAAC,kBAAkB,CACvB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,eAAe,GAAG,SAAS;IA8B9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO;IA2B9C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,MAAM,EAC1B,SAAS,EAAE,CAAC,EACZ,SAAS,EAAE,CAAC,GACX;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,EAAE;IAoC/D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAaxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiDG;IACH,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAmBnE;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAsD7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,EAC3B,WAAW,EAAE,KAAK,IAAI,EAAE,GAAG,KAAK,CAAC,EACjC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,CAAC;IA6CJ;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IA4C/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,sBAAsB;CAoFtC"}
@@ -1,4 +1,4 @@
1
- import { ENTITY_METADATA_KEY, PROPERTY_METADATA_KEY, PROPERTY_OPTIONS_METADATA_KEY } from './types.js';
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ENTITY_METADATA_KEY, PROPERTY_METADATA_KEY, PROPERTY_OPTIONS_METADATA_KEY } from './types.js';
2
2
  import { isEqualWith } from 'lodash-es';
3
3
  export class EntityUtils {
4
4
  /**
@@ -144,6 +144,258 @@ export class EntityUtils {
144
144
  return acc;
145
145
  }, {});
146
146
  }
147
+ /**
148
+ * Serializes an entity to a plain object, converting only properties decorated with @Property()
149
+ *
150
+ * @param entity - The entity instance to serialize
151
+ * @returns A plain object containing only the serialized decorated properties
152
+ *
153
+ * @remarks
154
+ * Serialization rules:
155
+ * - Only properties decorated with @Property() are included
156
+ * - If a property has a custom toJSON() method, it will be used
157
+ * - Nested entities are recursively serialized using EntityUtils.toJSON()
158
+ * - Arrays are mapped with toJSON() applied to each element
159
+ * - Date objects are serialized to ISO strings
160
+ * - bigint values are serialized to strings
161
+ * - undefined values are excluded from the output
162
+ * - null values are included in the output
163
+ * - Circular references are not supported (will cause stack overflow)
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * @Entity()
168
+ * class Address {
169
+ * @Property() street: string;
170
+ * @Property() city: string;
171
+ * }
172
+ *
173
+ * @Entity()
174
+ * class User {
175
+ * @Property() name: string;
176
+ * @Property() address: Address;
177
+ * @Property() createdAt: Date;
178
+ * undecorated: string; // Will not be serialized
179
+ * }
180
+ *
181
+ * const user = new User();
182
+ * user.name = 'John';
183
+ * user.address = new Address();
184
+ * user.address.street = '123 Main St';
185
+ * user.address.city = 'Boston';
186
+ * user.createdAt = new Date('2024-01-01');
187
+ * user.undecorated = 'ignored';
188
+ *
189
+ * const json = EntityUtils.toJSON(user);
190
+ * // {
191
+ * // name: 'John',
192
+ * // address: { street: '123 Main St', city: 'Boston' },
193
+ * // createdAt: '2024-01-01T00:00:00.000Z'
194
+ * // }
195
+ * ```
196
+ */ static toJSON(entity) {
197
+ const result = {};
198
+ const keys = this.getPropertyKeys(entity);
199
+ for (const key of keys){
200
+ const value = entity[key];
201
+ // Skip undefined values
202
+ if (value === undefined) {
203
+ continue;
204
+ }
205
+ const options = this.getPropertyOptions(entity, key);
206
+ result[key] = this.serializeValue(value, options);
207
+ }
208
+ return result;
209
+ }
210
+ /**
211
+ * Serializes a single value according to the toJSON rules
212
+ * @private
213
+ */ static serializeValue(value, options) {
214
+ if (value === null) {
215
+ return null;
216
+ }
217
+ if (value === undefined) {
218
+ return undefined;
219
+ }
220
+ const passthrough = options?.passthrough === true;
221
+ if (passthrough) {
222
+ return value;
223
+ }
224
+ if (Array.isArray(value)) {
225
+ if (options?.serialize) {
226
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
227
+ return value.map((item)=>options.serialize(item));
228
+ }
229
+ return value.map((item)=>this.serializeValue(item));
230
+ }
231
+ if (options?.serialize) {
232
+ return options.serialize(value);
233
+ }
234
+ if (value instanceof Date) {
235
+ return value.toISOString();
236
+ }
237
+ if (typeof value === 'bigint') {
238
+ return value.toString();
239
+ }
240
+ if (this.isEntity(value)) {
241
+ return this.toJSON(value);
242
+ }
243
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
244
+ return value;
245
+ }
246
+ throw new Error(`Cannot serialize value of type '${typeof value}'. Use passthrough: true in @Property() to explicitly allow serialization of unknown types.`);
247
+ }
248
+ /**
249
+ * Deserializes a plain object to an entity instance
250
+ *
251
+ * @param entityClass - The entity class constructor. Must accept a data object parameter.
252
+ * @param plainObject - The plain object to deserialize
253
+ * @returns A new instance of the entity with deserialized values
254
+ *
255
+ * @remarks
256
+ * Deserialization rules:
257
+ * - All @Property() decorators must include type metadata for parse() to work
258
+ * - Properties without type metadata will throw an error
259
+ * - Required properties (optional !== true) must be present and not null/undefined
260
+ * - Optional properties (optional === true) can be undefined or null
261
+ * - Arrays are supported with the array: true option
262
+ * - Nested entities are recursively deserialized
263
+ * - Type conversion is strict (no coercion)
264
+ * - Entity constructors must accept a required data parameter
265
+ *
266
+ * @example
267
+ * ```typescript
268
+ * @Entity()
269
+ * class User {
270
+ * @Property({ type: () => String }) name!: string;
271
+ * @Property({ type: () => Number }) age!: number;
272
+ *
273
+ * constructor(data: Partial<User>) {
274
+ * Object.assign(this, data);
275
+ * }
276
+ * }
277
+ *
278
+ * const json = { name: 'John', age: 30 };
279
+ * const user = EntityUtils.parse(User, json);
280
+ * ```
281
+ */ static parse(entityClass, plainObject) {
282
+ const keys = this.getPropertyKeys(entityClass.prototype);
283
+ const data = {};
284
+ for (const key of keys){
285
+ const options = this.getPropertyOptions(entityClass.prototype, key);
286
+ if (!options) {
287
+ throw new Error(`Property '${key}' has no metadata. This should not happen if @Property() was used correctly.`);
288
+ }
289
+ if (options.passthrough === true) {
290
+ const value = plainObject[key];
291
+ data[key] = value;
292
+ continue;
293
+ }
294
+ const value = plainObject[key];
295
+ const isOptional = options.optional === true;
296
+ if (!(key in plainObject)) {
297
+ if (!isOptional) {
298
+ throw new Error(`Property '${key}' is required but missing from input`);
299
+ }
300
+ continue;
301
+ }
302
+ if (value === null || value === undefined) {
303
+ if (!isOptional) {
304
+ throw new Error(`Property '${key}' cannot be null or undefined`);
305
+ }
306
+ data[key] = value;
307
+ continue;
308
+ }
309
+ data[key] = this.deserializeValue(value, options, key);
310
+ }
311
+ return new entityClass(data);
312
+ }
313
+ /**
314
+ * Deserializes a single value according to the type metadata
315
+ * @private
316
+ */ static deserializeValue(value, options, propertyKey) {
317
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
318
+ const typeConstructor = options.type();
319
+ const isArray = options.array === true;
320
+ const isSparse = options.sparse === true;
321
+ if (isArray) {
322
+ if (!Array.isArray(value)) {
323
+ throw new Error(`Property '${propertyKey}' expects an array but received ${typeof value}`);
324
+ }
325
+ return value.map((item, index)=>{
326
+ if (item === null || item === undefined) {
327
+ if (!isSparse) {
328
+ throw new Error(`Property '${propertyKey}[${index}]' cannot be null or undefined. Use sparse: true to allow null/undefined elements in arrays.`);
329
+ }
330
+ return item;
331
+ }
332
+ if (options.deserialize) {
333
+ return options.deserialize(item);
334
+ }
335
+ return this.deserializeSingleValue(item, typeConstructor, `${propertyKey}[${index}]`);
336
+ });
337
+ }
338
+ if (options.deserialize) {
339
+ return options.deserialize(value);
340
+ }
341
+ return this.deserializeSingleValue(value, typeConstructor, propertyKey);
342
+ }
343
+ /**
344
+ * Deserializes a single non-array value
345
+ * @private
346
+ */ static deserializeSingleValue(value, typeConstructor, propertyKey) {
347
+ if (typeConstructor === String) {
348
+ if (typeof value !== 'string') {
349
+ throw new Error(`Property '${propertyKey}' expects a string but received ${typeof value}`);
350
+ }
351
+ return value;
352
+ }
353
+ if (typeConstructor === Number) {
354
+ if (typeof value !== 'number') {
355
+ throw new Error(`Property '${propertyKey}' expects a number but received ${typeof value}`);
356
+ }
357
+ return value;
358
+ }
359
+ if (typeConstructor === Boolean) {
360
+ if (typeof value !== 'boolean') {
361
+ throw new Error(`Property '${propertyKey}' expects a boolean but received ${typeof value}`);
362
+ }
363
+ return value;
364
+ }
365
+ if (typeConstructor === BigInt) {
366
+ if (typeof value === 'bigint') {
367
+ return value;
368
+ }
369
+ if (typeof value === 'string') {
370
+ try {
371
+ return BigInt(value);
372
+ } catch {
373
+ throw new Error(`Property '${propertyKey}' cannot parse '${value}' as BigInt`);
374
+ }
375
+ }
376
+ throw new Error(`Property '${propertyKey}' expects a bigint or string but received ${typeof value}`);
377
+ }
378
+ if (typeConstructor === Date) {
379
+ if (value instanceof Date) {
380
+ return value;
381
+ }
382
+ if (typeof value === 'string') {
383
+ const date = new Date(value);
384
+ if (isNaN(date.getTime())) {
385
+ throw new Error(`Property '${propertyKey}' cannot parse '${value}' as Date`);
386
+ }
387
+ return date;
388
+ }
389
+ throw new Error(`Property '${propertyKey}' expects a Date or ISO string but received ${typeof value}`);
390
+ }
391
+ if (this.isEntity(typeConstructor)) {
392
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
393
+ throw new Error(`Property '${propertyKey}' expects an object but received ${typeof value}`);
394
+ }
395
+ return this.parse(typeConstructor, value);
396
+ }
397
+ throw new Error(`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.`);
398
+ }
147
399
  }
148
400
 
149
401
  //# sourceMappingURL=entity-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/entity-utils.ts"],"sourcesContent":["import {\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"],"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"],"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 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,21 +1,126 @@
1
- import { PropertyOptions } from './types.js';
1
+ import { AnyCtor, type CtorLike, PropertyOptions } from './types.js';
2
2
  /**
3
3
  * Property decorator that marks class properties with metadata.
4
4
  * This decorator can be used to identify and track properties within classes.
5
5
  *
6
- * @param options - Optional configuration for the property
6
+ * @param options - Configuration for the property (type is required)
7
7
  *
8
8
  * @example
9
9
  * class User {
10
- * @Property()
10
+ * @Property({ type: () => String })
11
11
  * name: string;
12
12
  *
13
- * @Property({ equals: (a, b) => a.toLowerCase() === b.toLowerCase() })
13
+ * @Property({ type: () => String, equals: (a, b) => a.toLowerCase() === b.toLowerCase() })
14
14
  * email: string;
15
15
  *
16
- * @Property()
16
+ * @Property({ type: () => Number })
17
17
  * age: number;
18
18
  * }
19
19
  */
20
- export declare function Property<T = any>(options?: PropertyOptions<T>): PropertyDecorator;
20
+ export declare function Property<T, C extends CtorLike<T>>(options: PropertyOptions<T, C>): PropertyDecorator;
21
+ /**
22
+ * Helper decorator for string properties
23
+ * @example
24
+ * class User {
25
+ * @StringProperty()
26
+ * name!: string;
27
+ *
28
+ * @StringProperty({ optional: true })
29
+ * nickname?: string;
30
+ * }
31
+ */
32
+ export declare function StringProperty(options?: Omit<PropertyOptions<string, StringConstructor>, 'type'>): PropertyDecorator;
33
+ /**
34
+ * Helper decorator for number properties
35
+ * @example
36
+ * class User {
37
+ * @NumberProperty()
38
+ * age!: number;
39
+ *
40
+ * @NumberProperty({ optional: true })
41
+ * score?: number;
42
+ * }
43
+ */
44
+ export declare function NumberProperty(options?: Omit<PropertyOptions<number, NumberConstructor>, 'type'>): PropertyDecorator;
45
+ /**
46
+ * Helper decorator for boolean properties
47
+ * @example
48
+ * class User {
49
+ * @BooleanProperty()
50
+ * active!: boolean;
51
+ *
52
+ * @BooleanProperty({ optional: true })
53
+ * verified?: boolean;
54
+ * }
55
+ */
56
+ export declare function BooleanProperty(options?: Omit<PropertyOptions<boolean, BooleanConstructor>, 'type'>): PropertyDecorator;
57
+ /**
58
+ * Helper decorator for Date properties
59
+ * @example
60
+ * class User {
61
+ * @DateProperty()
62
+ * createdAt!: Date;
63
+ *
64
+ * @DateProperty({ optional: true })
65
+ * deletedAt?: Date;
66
+ * }
67
+ */
68
+ export declare function DateProperty(options?: Omit<PropertyOptions<Date, DateConstructor>, 'type'>): PropertyDecorator;
69
+ /**
70
+ * Helper decorator for BigInt properties
71
+ * @example
72
+ * class User {
73
+ * @BigIntProperty()
74
+ * id!: bigint;
75
+ *
76
+ * @BigIntProperty({ optional: true })
77
+ * balance?: bigint;
78
+ * }
79
+ */
80
+ export declare function BigIntProperty(options?: Omit<PropertyOptions<bigint, BigIntConstructor>, 'type'>): PropertyDecorator;
81
+ /**
82
+ * Helper decorator for entity properties
83
+ * @example
84
+ * class User {
85
+ * @EntityProperty(() => Address)
86
+ * address!: Address;
87
+ *
88
+ * @EntityProperty(() => Profile, { optional: true })
89
+ * profile?: Profile;
90
+ * }
91
+ */
92
+ export declare function EntityProperty<T, C extends AnyCtor<T> & {
93
+ new (data: any): T;
94
+ }>(type: () => C, options?: Omit<PropertyOptions<T, C>, 'type'>): PropertyDecorator;
95
+ /**
96
+ * Helper decorator for array properties
97
+ * @example
98
+ * class User {
99
+ * @ArrayProperty(() => String)
100
+ * tags!: string[];
101
+ *
102
+ * @ArrayProperty(() => Phone)
103
+ * phones!: Phone[];
104
+ *
105
+ * @ArrayProperty(() => Number, { optional: true })
106
+ * scores?: number[];
107
+ *
108
+ * @ArrayProperty(() => String, { sparse: true })
109
+ * sparseList!: (string | null)[];
110
+ * }
111
+ */
112
+ export declare function ArrayProperty<T, C extends CtorLike<T>>(type: () => C, options?: Omit<PropertyOptions<T, C>, 'type' | 'array'>): PropertyDecorator;
113
+ /**
114
+ * Helper decorator for passthrough properties that bypass type validation.
115
+ * Use this for generic types like Record<string, unknown>, any, or custom objects.
116
+ * @example
117
+ * class Config {
118
+ * @PassthroughProperty()
119
+ * metadata!: Record<string, unknown>;
120
+ *
121
+ * @PassthroughProperty()
122
+ * customData!: any;
123
+ * }
124
+ */
125
+ export declare function PassthroughProperty(): PropertyDecorator;
21
126
  //# sourceMappingURL=property.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"property.d.ts","sourceRoot":"","sources":["../../src/lib/property.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,GAAG,EAC9B,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,GAC3B,iBAAiB,CAiCnB"}
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,42 +1,195 @@
1
- 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 { PROPERTY_METADATA_KEY, PROPERTY_OPTIONS_METADATA_KEY } from './types.js';
2
2
  /**
3
3
  * Property decorator that marks class properties with metadata.
4
4
  * This decorator can be used to identify and track properties within classes.
5
5
  *
6
- * @param options - Optional configuration for the property
6
+ * @param options - Configuration for the property (type is required)
7
7
  *
8
8
  * @example
9
9
  * class User {
10
- * @Property()
10
+ * @Property({ type: () => String })
11
11
  * name: string;
12
12
  *
13
- * @Property({ equals: (a, b) => a.toLowerCase() === b.toLowerCase() })
13
+ * @Property({ type: () => String, equals: (a, b) => a.toLowerCase() === b.toLowerCase() })
14
14
  * email: string;
15
15
  *
16
- * @Property()
16
+ * @Property({ type: () => Number })
17
17
  * age: number;
18
18
  * }
19
19
  */ export function Property(options) {
20
20
  return (target, propertyKey)=>{
21
- // Only support string property keys
22
21
  if (typeof propertyKey !== 'string') {
23
22
  return;
24
23
  }
25
- // Get existing metadata from own property only (not from prototype chain)
26
24
  const existingProperties = Reflect.getOwnMetadata(PROPERTY_METADATA_KEY, target) || [];
27
- // Add this property if not already tracked
28
25
  if (!existingProperties.includes(propertyKey)) {
29
26
  existingProperties.push(propertyKey);
30
27
  }
31
- // Store updated metadata on the target itself
32
28
  Reflect.defineMetadata(PROPERTY_METADATA_KEY, existingProperties, target);
33
- // Store property options if provided
34
- if (options) {
35
- const existingOptions = Reflect.getOwnMetadata(PROPERTY_OPTIONS_METADATA_KEY, target) || {};
36
- existingOptions[propertyKey] = options;
37
- Reflect.defineMetadata(PROPERTY_OPTIONS_METADATA_KEY, existingOptions, target);
29
+ if (options.passthrough === true) {
30
+ if (options.array === true) {
31
+ throw new Error(`Property '${propertyKey}' has passthrough: true and array: true. Passthrough cannot be combined with array.`);
32
+ }
33
+ if (options.optional === true) {
34
+ throw new Error(`Property '${propertyKey}' has passthrough: true and optional: true. Passthrough cannot be combined with optional.`);
35
+ }
36
+ if (options.sparse === true) {
37
+ throw new Error(`Property '${propertyKey}' has passthrough: true and sparse: true. Passthrough cannot be combined with sparse.`);
38
+ }
39
+ if (options.serialize !== undefined || options.deserialize !== undefined) {
40
+ throw new Error(`Property '${propertyKey}' has passthrough: true and custom serialize/deserialize functions. Passthrough cannot be combined with serialize or deserialize.`);
41
+ }
38
42
  }
43
+ if (options.sparse === true && options.array !== true) {
44
+ throw new Error(`Property '${propertyKey}' has sparse: true but array is not true. The sparse option only applies to arrays.`);
45
+ }
46
+ // Validate serialize/deserialize pairing
47
+ const hasSerialize = options.serialize !== undefined;
48
+ const hasDeserialize = options.deserialize !== undefined;
49
+ if (hasSerialize !== hasDeserialize) {
50
+ throw new Error(`Property '${propertyKey}' must define both serialize and deserialize functions, or neither. Found only ${hasSerialize ? 'serialize' : 'deserialize'}.`);
51
+ }
52
+ const existingOptions = Reflect.getOwnMetadata(PROPERTY_OPTIONS_METADATA_KEY, target) || {};
53
+ existingOptions[propertyKey] = options;
54
+ Reflect.defineMetadata(PROPERTY_OPTIONS_METADATA_KEY, existingOptions, target);
39
55
  };
40
56
  }
57
+ /**
58
+ * Helper decorator for string properties
59
+ * @example
60
+ * class User {
61
+ * @StringProperty()
62
+ * name!: string;
63
+ *
64
+ * @StringProperty({ optional: true })
65
+ * nickname?: string;
66
+ * }
67
+ */ export function StringProperty(options) {
68
+ return Property({
69
+ ...options,
70
+ type: ()=>String
71
+ });
72
+ }
73
+ /**
74
+ * Helper decorator for number properties
75
+ * @example
76
+ * class User {
77
+ * @NumberProperty()
78
+ * age!: number;
79
+ *
80
+ * @NumberProperty({ optional: true })
81
+ * score?: number;
82
+ * }
83
+ */ export function NumberProperty(options) {
84
+ return Property({
85
+ ...options,
86
+ type: ()=>Number
87
+ });
88
+ }
89
+ /**
90
+ * Helper decorator for boolean properties
91
+ * @example
92
+ * class User {
93
+ * @BooleanProperty()
94
+ * active!: boolean;
95
+ *
96
+ * @BooleanProperty({ optional: true })
97
+ * verified?: boolean;
98
+ * }
99
+ */ export function BooleanProperty(options) {
100
+ return Property({
101
+ ...options,
102
+ type: ()=>Boolean
103
+ });
104
+ }
105
+ /**
106
+ * Helper decorator for Date properties
107
+ * @example
108
+ * class User {
109
+ * @DateProperty()
110
+ * createdAt!: Date;
111
+ *
112
+ * @DateProperty({ optional: true })
113
+ * deletedAt?: Date;
114
+ * }
115
+ */ export function DateProperty(options) {
116
+ return Property({
117
+ ...options,
118
+ type: ()=>Date
119
+ });
120
+ }
121
+ /**
122
+ * Helper decorator for BigInt properties
123
+ * @example
124
+ * class User {
125
+ * @BigIntProperty()
126
+ * id!: bigint;
127
+ *
128
+ * @BigIntProperty({ optional: true })
129
+ * balance?: bigint;
130
+ * }
131
+ */ export function BigIntProperty(options) {
132
+ return Property({
133
+ ...options,
134
+ type: ()=>BigInt
135
+ });
136
+ }
137
+ /**
138
+ * Helper decorator for entity properties
139
+ * @example
140
+ * class User {
141
+ * @EntityProperty(() => Address)
142
+ * address!: Address;
143
+ *
144
+ * @EntityProperty(() => Profile, { optional: true })
145
+ * profile?: Profile;
146
+ * }
147
+ */ export function EntityProperty(type, options) {
148
+ return Property({
149
+ ...options,
150
+ type
151
+ });
152
+ }
153
+ /**
154
+ * Helper decorator for array properties
155
+ * @example
156
+ * class User {
157
+ * @ArrayProperty(() => String)
158
+ * tags!: string[];
159
+ *
160
+ * @ArrayProperty(() => Phone)
161
+ * phones!: Phone[];
162
+ *
163
+ * @ArrayProperty(() => Number, { optional: true })
164
+ * scores?: number[];
165
+ *
166
+ * @ArrayProperty(() => String, { sparse: true })
167
+ * sparseList!: (string | null)[];
168
+ * }
169
+ */ export function ArrayProperty(type, options) {
170
+ return Property({
171
+ ...options,
172
+ type,
173
+ array: true
174
+ });
175
+ }
176
+ /**
177
+ * Helper decorator for passthrough properties that bypass type validation.
178
+ * Use this for generic types like Record<string, unknown>, any, or custom objects.
179
+ * @example
180
+ * class Config {
181
+ * @PassthroughProperty()
182
+ * metadata!: Record<string, unknown>;
183
+ *
184
+ * @PassthroughProperty()
185
+ * customData!: any;
186
+ * }
187
+ */ export function PassthroughProperty() {
188
+ // Use a dummy type since type is mandatory but not used with passthrough
189
+ return Property({
190
+ type: ()=>Object,
191
+ passthrough: true
192
+ });
193
+ }
41
194
 
42
195
  //# sourceMappingURL=property.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/property.ts"],"sourcesContent":["import {\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 - Optional configuration for the property\n *\n * @example\n * class User {\n * @Property()\n * name: string;\n *\n * @Property({ equals: (a, b) => a.toLowerCase() === b.toLowerCase() })\n * email: string;\n *\n * @Property()\n * age: number;\n * }\n */\nexport function Property<T = any>(\n options?: PropertyOptions<T>,\n): PropertyDecorator {\n return (target: object, propertyKey: string | symbol): void => {\n // Only support string property keys\n if (typeof propertyKey !== 'string') {\n return;\n }\n\n // Get existing metadata from own property only (not from prototype chain)\n const existingProperties: string[] =\n Reflect.getOwnMetadata(PROPERTY_METADATA_KEY, target) || [];\n\n // Add this property if not already tracked\n if (!existingProperties.includes(propertyKey)) {\n existingProperties.push(propertyKey);\n }\n\n // Store updated metadata on the target itself\n Reflect.defineMetadata(PROPERTY_METADATA_KEY, existingProperties, target);\n\n // Store property options if provided\n if (options) {\n const existingOptions: Record<string, PropertyOptions> =\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"],"names":["PROPERTY_METADATA_KEY","PROPERTY_OPTIONS_METADATA_KEY","Property","options","target","propertyKey","existingProperties","Reflect","getOwnMetadata","includes","push","defineMetadata","existingOptions"],"mappings":"AAAA,SACEA,qBAAqB,EACrBC,6BAA6B,QAExB,aAAa;AAEpB;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASC,SACdC,OAA4B;IAE5B,OAAO,CAACC,QAAgBC;QACtB,oCAAoC;QACpC,IAAI,OAAOA,gBAAgB,UAAU;YACnC;QACF;QAEA,0EAA0E;QAC1E,MAAMC,qBACJC,QAAQC,cAAc,CAACR,uBAAuBI,WAAW,EAAE;QAE7D,2CAA2C;QAC3C,IAAI,CAACE,mBAAmBG,QAAQ,CAACJ,cAAc;YAC7CC,mBAAmBI,IAAI,CAACL;QAC1B;QAEA,8CAA8C;QAC9CE,QAAQI,cAAc,CAACX,uBAAuBM,oBAAoBF;QAElE,qCAAqC;QACrC,IAAID,SAAS;YACX,MAAMS,kBACJL,QAAQC,cAAc,CAACP,+BAA+BG,WAAW,CAAC;YAEpEQ,eAAe,CAACP,YAAY,GAAGF;YAE/BI,QAAQI,cAAc,CACpBV,+BACAW,iBACAR;QAEJ;IACF;AACF"}
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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"}
@@ -10,16 +10,96 @@ export declare const PROPERTY_OPTIONS_METADATA_KEY: unique symbol;
10
10
  * Metadata key used to store entity information
11
11
  */
12
12
  export declare const ENTITY_METADATA_KEY: unique symbol;
13
+ export type AnyCtor<T = any> = Function & {
14
+ prototype: T;
15
+ };
16
+ export type BuiltinCtors = StringConstructor | NumberConstructor | BooleanConstructor | BigIntConstructor | SymbolConstructor | DateConstructor;
17
+ export type CtorLike<T> = AnyCtor<T> | BuiltinCtors;
18
+ export type InstanceOfCtorLike<C> = C extends StringConstructor ? string : C extends NumberConstructor ? number : C extends BooleanConstructor ? boolean : C extends BigIntConstructor ? bigint : C extends SymbolConstructor ? symbol : C extends DateConstructor ? Date : C extends AnyCtor<infer T> ? T : never;
13
19
  /**
14
20
  * Options for the Property decorator
15
21
  */
16
- export interface PropertyOptions<T = any> {
22
+ export interface PropertyOptions<T = any, C extends CtorLike<T> = AnyCtor<T> | BuiltinCtors> {
17
23
  /**
18
24
  * Custom equality comparison function for this property
19
25
  * @param a - First value to compare
20
26
  * @param b - Second value to compare
21
27
  * @returns true if values are equal, false otherwise
22
28
  */
23
- equals?: (a: T, b: T) => boolean;
29
+ equals?: (a: InstanceOfCtorLike<C>, b: InstanceOfCtorLike<C>) => boolean;
30
+ /**
31
+ * Type constructor for this property. Required for EntityUtils.parse() support.
32
+ * Use a function that returns the type constructor to support forward references.
33
+ * @example
34
+ * @Property({ type: () => String })
35
+ * name!: string;
36
+ *
37
+ * @Property({ type: () => Address })
38
+ * address!: Address;
39
+ */
40
+ type: () => C;
41
+ /**
42
+ * Whether this property is an array. Defaults to false.
43
+ * When true, the deserializer will map over array elements.
44
+ * @example
45
+ * @Property({ type: () => String, array: true })
46
+ * tags!: string[];
47
+ */
48
+ array?: boolean;
49
+ /**
50
+ * Whether this property is optional. Defaults to false.
51
+ * When true, the property can be undefined or null.
52
+ * When false, the property must be present and not null/undefined.
53
+ * @example
54
+ * @Property({ type: () => String, optional: true })
55
+ * nickname?: string;
56
+ */
57
+ optional?: boolean;
58
+ /**
59
+ * Whether the array can contain null/undefined elements. Defaults to false.
60
+ * Only applicable when array is true.
61
+ * When false (default), null/undefined elements will cause an error.
62
+ * When true, null/undefined elements are allowed in the array.
63
+ * @example
64
+ * @Property({ type: () => String, array: true, sparse: true })
65
+ * tags!: (string | null)[];
66
+ */
67
+ sparse?: boolean;
68
+ /**
69
+ * Whether to bypass type validation and pass values through as-is.
70
+ * Use this for generic types like Record<string, unknown> or any.
71
+ * When true, no type checking or transformation is performed.
72
+ * Also bypasses any custom serialize/deserialize callbacks.
73
+ * @example
74
+ * @Property({ passthrough: true })
75
+ * metadata!: Record<string, unknown>;
76
+ */
77
+ passthrough?: boolean;
78
+ /**
79
+ * Custom serialization function to convert the property value to JSON-compatible format.
80
+ * Must be paired with deserialize - both must be defined together or both omitted.
81
+ * Not used when passthrough is true.
82
+ * @example
83
+ * @Property({
84
+ * type: () => MyClass,
85
+ * serialize: (value) => ({ data: value.toData() }),
86
+ * deserialize: (json) => MyClass.fromData(json.data)
87
+ * })
88
+ * myProperty!: MyClass;
89
+ */
90
+ serialize?: (value: InstanceOfCtorLike<C>) => unknown;
91
+ /**
92
+ * Custom deserialization function to convert JSON data back to the property type.
93
+ * Must be paired with serialize - both must be defined together or both omitted.
94
+ * Not used when passthrough is true.
95
+ * @example
96
+ * @Property({
97
+ * type: () => MyClass,
98
+ * serialize: (value) => ({ data: value.toData() }),
99
+ * deserialize: (json) => MyClass.fromData(json.data)
100
+ * })
101
+ * myProperty!: MyClass;
102
+ */
103
+ deserialize?: (serialized: unknown) => InstanceOfCtorLike<C>;
24
104
  }
25
105
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,qBAAqB,eAA8B,CAAC;AAEjE;;GAEG;AACH,eAAO,MAAM,6BAA6B,eAEzC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,eAA4B,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,GAAG;IACtC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC;CAClC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,qBAAqB,eAA8B,CAAC;AAEjE;;GAEG;AACH,eAAO,MAAM,6BAA6B,eAEzC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,eAA4B,CAAC;AAG7D,MAAM,MAAM,OAAO,CAAC,CAAC,GAAG,GAAG,IAAI,QAAQ,GAAG;IAAE,SAAS,EAAE,CAAC,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,YAAY,GACpB,iBAAiB,GACjB,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,CAAC;AAEpB,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC;AAEpD,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,CAAC,SAAS,iBAAiB,GAC3D,MAAM,GACN,CAAC,SAAS,iBAAiB,GACzB,MAAM,GACN,CAAC,SAAS,kBAAkB,GAC1B,OAAO,GACP,CAAC,SAAS,iBAAiB,GACzB,MAAM,GACN,CAAC,SAAS,iBAAiB,GACzB,MAAM,GACN,CAAC,SAAS,eAAe,GACvB,IAAI,GACJ,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GACxB,CAAC,GACD,KAAK,CAAC;AAEtB;;GAEG;AACH,MAAM,WAAW,eAAe,CAC9B,CAAC,GAAG,GAAG,EACP,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,YAAY;IAEjD;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;IAEzE;;;;;;;;;OASG;IACH,IAAI,EAAE,MAAM,CAAC,CAAC;IAEd;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;IAEtD;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,kBAAkB,CAAC,CAAC,CAAC,CAAC;CAC9D"}
package/dist/lib/types.js CHANGED
@@ -1,4 +1,4 @@
1
- /**
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-wrapper-object-types */ /**
2
2
  * Metadata key used to store property information
3
3
  */ export const PROPERTY_METADATA_KEY = Symbol('property:metadata');
4
4
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/types.ts"],"sourcesContent":["/**\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 * Options for the Property decorator\n */\nexport interface PropertyOptions<T = any> {\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: T, b: T) => boolean;\n}\n"],"names":["PROPERTY_METADATA_KEY","Symbol","PROPERTY_OPTIONS_METADATA_KEY","ENTITY_METADATA_KEY"],"mappings":"AAAA;;CAEC,GACD,OAAO,MAAMA,wBAAwBC,OAAO,qBAAqB;AAEjE;;CAEC,GACD,OAAO,MAAMC,gCAAgCD,OAC3C,6BACA;AAEF;;CAEC,GACD,OAAO,MAAME,sBAAsBF,OAAO,mBAAmB"}
1
+ {"version":3,"sources":["../../src/lib/types.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-wrapper-object-types */\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// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\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\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 * 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"],"names":["PROPERTY_METADATA_KEY","Symbol","PROPERTY_OPTIONS_METADATA_KEY","ENTITY_METADATA_KEY"],"mappings":"AAAA,qDAAqD,GACrD,6DAA6D,GAC7D;;CAEC,GACD,OAAO,MAAMA,wBAAwBC,OAAO,qBAAqB;AAEjE;;CAEC,GACD,OAAO,MAAMC,gCAAgCD,OAC3C,6BACA;AAEF;;CAEC,GACD,OAAO,MAAME,sBAAsBF,OAAO,mBAAmB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtpaulino/entity",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -47,7 +47,7 @@
47
47
  }
48
48
  },
49
49
  "dependencies": {
50
- "@rtpaulino/core": "^0.11.0",
50
+ "@rtpaulino/core": "^0.13.0",
51
51
  "@swc/helpers": "~0.5.18",
52
52
  "lodash-es": "^4.17.22"
53
53
  },