@woltz/rich-domain 1.2.0 → 1.2.2

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 (143) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/aggregate-changes.d.ts +164 -0
  3. package/dist/aggregate-changes.d.ts.map +1 -0
  4. package/dist/aggregate-changes.js +281 -0
  5. package/dist/aggregate-changes.js.map +1 -0
  6. package/dist/base-entity.d.ts +32 -8
  7. package/dist/base-entity.d.ts.map +1 -1
  8. package/dist/base-entity.js +86 -93
  9. package/dist/base-entity.js.map +1 -1
  10. package/dist/change-tracker.d.ts +97 -0
  11. package/dist/change-tracker.d.ts.map +1 -0
  12. package/dist/change-tracker.js +758 -0
  13. package/dist/change-tracker.js.map +1 -0
  14. package/dist/constants.d.ts +7 -1
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +65 -0
  17. package/dist/constants.js.map +1 -1
  18. package/dist/criteria.d.ts +3 -3
  19. package/dist/criteria.d.ts.map +1 -1
  20. package/dist/criteria.js +6 -4
  21. package/dist/criteria.js.map +1 -1
  22. package/dist/crypto.d.ts +3 -0
  23. package/dist/crypto.d.ts.map +1 -0
  24. package/dist/crypto.js +29 -0
  25. package/dist/crypto.js.map +1 -0
  26. package/dist/domain-event.d.ts.map +1 -1
  27. package/dist/domain-event.js +0 -3
  28. package/dist/domain-event.js.map +1 -1
  29. package/dist/entity-changes.d.ts +84 -0
  30. package/dist/entity-changes.d.ts.map +1 -0
  31. package/dist/entity-changes.js +131 -0
  32. package/dist/entity-changes.js.map +1 -0
  33. package/dist/entity-schema-registry.d.ts +148 -0
  34. package/dist/entity-schema-registry.d.ts.map +1 -0
  35. package/dist/entity-schema-registry.js +213 -0
  36. package/dist/entity-schema-registry.js.map +1 -0
  37. package/dist/entity.d.ts +0 -6
  38. package/dist/entity.d.ts.map +1 -1
  39. package/dist/entity.js +0 -9
  40. package/dist/entity.js.map +1 -1
  41. package/dist/id.d.ts +11 -10
  42. package/dist/id.d.ts.map +1 -1
  43. package/dist/id.js +4 -28
  44. package/dist/id.js.map +1 -1
  45. package/dist/index.d.ts +9 -5
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +8 -11
  48. package/dist/index.js.map +1 -1
  49. package/dist/mapper.d.ts +1 -1
  50. package/dist/mapper.d.ts.map +1 -1
  51. package/dist/mapper.js.map +1 -1
  52. package/dist/paginated-result.d.ts.map +1 -1
  53. package/dist/paginated-result.js +0 -15
  54. package/dist/paginated-result.js.map +1 -1
  55. package/dist/repository/base-repository.d.ts +7 -33
  56. package/dist/repository/base-repository.d.ts.map +1 -1
  57. package/dist/repository/base-repository.js +0 -27
  58. package/dist/repository/base-repository.js.map +1 -1
  59. package/dist/repository/index.d.ts.map +1 -1
  60. package/dist/repository/index.js +0 -6
  61. package/dist/repository/index.js.map +1 -1
  62. package/dist/repository/unit-of-work.d.ts +0 -25
  63. package/dist/repository/unit-of-work.d.ts.map +1 -1
  64. package/dist/repository/unit-of-work.js +0 -28
  65. package/dist/repository/unit-of-work.js.map +1 -1
  66. package/dist/types/change-tracker.d.ts +196 -0
  67. package/dist/types/change-tracker.d.ts.map +1 -0
  68. package/dist/types/change-tracker.js +2 -0
  69. package/dist/types/change-tracker.js.map +1 -0
  70. package/dist/types/criteria.d.ts +5 -1
  71. package/dist/types/criteria.d.ts.map +1 -1
  72. package/dist/types/domain.d.ts +4 -6
  73. package/dist/types/domain.d.ts.map +1 -1
  74. package/dist/types/index.d.ts +1 -1
  75. package/dist/types/index.d.ts.map +1 -1
  76. package/dist/types/index.js +1 -1
  77. package/dist/types/index.js.map +1 -1
  78. package/dist/types/utils.d.ts +0 -1
  79. package/dist/types/utils.d.ts.map +1 -1
  80. package/dist/utils/criteria-operator-validation.d.ts +1 -0
  81. package/dist/utils/criteria-operator-validation.d.ts.map +1 -1
  82. package/dist/utils/criteria-operator-validation.js +39 -17
  83. package/dist/utils/criteria-operator-validation.js.map +1 -1
  84. package/dist/validation-error.d.ts.map +1 -1
  85. package/dist/validation-error.js +1 -6
  86. package/dist/validation-error.js.map +1 -1
  87. package/dist/value-object.d.ts +57 -8
  88. package/dist/value-object.d.ts.map +1 -1
  89. package/dist/value-object.js +49 -22
  90. package/dist/value-object.js.map +1 -1
  91. package/package.json +2 -1
  92. package/src/aggregate-changes.ts +335 -0
  93. package/src/base-entity.ts +102 -109
  94. package/src/change-tracker.ts +1062 -0
  95. package/src/constants.ts +75 -1
  96. package/src/criteria.ts +11 -4
  97. package/src/crypto.ts +31 -0
  98. package/src/domain-event.ts +0 -4
  99. package/src/entity-changes.ts +146 -0
  100. package/src/entity-schema-registry.ts +255 -0
  101. package/src/entity.ts +0 -11
  102. package/src/id.ts +17 -26
  103. package/src/index.ts +15 -19
  104. package/src/mapper.ts +4 -1
  105. package/src/paginated-result.ts +0 -21
  106. package/src/repository/base-repository.ts +7 -38
  107. package/src/repository/index.ts +0 -9
  108. package/src/repository/unit-of-work.ts +0 -29
  109. package/src/types/change-tracker.ts +233 -0
  110. package/src/types/criteria.ts +6 -1
  111. package/src/types/domain.ts +4 -8
  112. package/src/types/index.ts +1 -1
  113. package/src/types/utils.ts +0 -9
  114. package/src/utils/criteria-operator-validation.ts +57 -19
  115. package/src/validation-error.ts +1 -7
  116. package/src/value-object.ts +84 -24
  117. package/tests/aggregate-changes.test.ts +284 -0
  118. package/tests/criteria.test.ts +122 -161
  119. package/tests/entity-equality.test.ts +38 -61
  120. package/tests/entity-schema-registry.test.ts +382 -0
  121. package/tests/entity-validation.test.ts +7 -94
  122. package/tests/history-tracker.spec.ts +349 -617
  123. package/tests/id.test.ts +41 -44
  124. package/tests/load-test/data.json +346041 -0
  125. package/tests/load-test/entities.ts +97 -0
  126. package/tests/load-test/generate-data.ts +81 -0
  127. package/tests/load-test/lead-to-domain.mapper.ts +24 -0
  128. package/tests/load-test/load.test.ts +38 -0
  129. package/tests/repository.test.ts +30 -54
  130. package/tests/to-json.test.ts +14 -18
  131. package/tests/utils.ts +138 -102
  132. package/tests/value-objects.test.ts +57 -29
  133. package/dist/deep-proxy.d.ts +0 -36
  134. package/dist/deep-proxy.d.ts.map +0 -1
  135. package/dist/deep-proxy.js +0 -384
  136. package/dist/deep-proxy.js.map +0 -1
  137. package/dist/types/history-tracker.d.ts +0 -36
  138. package/dist/types/history-tracker.d.ts.map +0 -1
  139. package/dist/types/history-tracker.js +0 -2
  140. package/dist/types/history-tracker.js.map +0 -1
  141. package/src/deep-proxy.ts +0 -447
  142. package/src/types/history-tracker.ts +0 -45
  143. package/tests/entity.test.ts +0 -33
package/src/constants.ts CHANGED
@@ -1,7 +1,81 @@
1
- import { ValidationConfig } from ".";
1
+ import {
2
+ ArrayOperators,
3
+ BooleanOperators,
4
+ DateOperators,
5
+ FilterOperator,
6
+ NumberOperators,
7
+ StringOperators,
8
+ ValidationConfig,
9
+ } from ".";
2
10
 
3
11
  export const DEFAULT_VALIDATION_CONFIG: Required<ValidationConfig> = {
4
12
  onCreate: true,
5
13
  onUpdate: true,
6
14
  throwOnError: true,
7
15
  };
16
+
17
+ export const ARRAY_OPERATORS: ArrayOperators[] = [
18
+ "in",
19
+ "notIn",
20
+ "isNull",
21
+ "isNotNull",
22
+ ];
23
+ export const BOOLEAN_OPERATORS: BooleanOperators[] = [
24
+ "equals",
25
+ "notEquals",
26
+ "isNull",
27
+ "isNotNull",
28
+ ];
29
+ export const DATE_OPERATORS: DateOperators[] = [
30
+ "equals",
31
+ "notEquals",
32
+ "greaterThan",
33
+ "greaterThanOrEqual",
34
+ "lessThan",
35
+ "lessThanOrEqual",
36
+ "in",
37
+ "notIn",
38
+ "between",
39
+ "isNull",
40
+ "isNotNull",
41
+ ];
42
+ export const NUMBER_OPERATORS: NumberOperators[] = [
43
+ "equals",
44
+ "notEquals",
45
+ "greaterThan",
46
+ "greaterThanOrEqual",
47
+ "lessThan",
48
+ "lessThanOrEqual",
49
+ "in",
50
+ "notIn",
51
+ "between",
52
+ "isNull",
53
+ "isNotNull",
54
+ ];
55
+ export const STRING_OPERATORS: StringOperators[] = [
56
+ "equals",
57
+ "notEquals",
58
+ "contains",
59
+ "startsWith",
60
+ "endsWith",
61
+ "in",
62
+ "notIn",
63
+ "isNull",
64
+ "isNotNull",
65
+ ];
66
+ export const FILTER_OPERATORS: FilterOperator[] = [
67
+ "equals",
68
+ "notEquals",
69
+ "greaterThan",
70
+ "greaterThanOrEqual",
71
+ "lessThan",
72
+ "lessThanOrEqual",
73
+ "contains",
74
+ "startsWith",
75
+ "endsWith",
76
+ "in",
77
+ "notIn",
78
+ "between",
79
+ "isNull",
80
+ "isNotNull",
81
+ ];
package/src/criteria.ts CHANGED
@@ -13,11 +13,13 @@ import {
13
13
  PathValue,
14
14
  Search,
15
15
  TypedFilter,
16
+ TypedOrder,
16
17
  } from "./types";
17
18
  import {
18
19
  isValidOperatorForType,
19
20
  getValidOperatorsForType,
20
21
  isOperator,
22
+ sanitizeFieldValue,
21
23
  } from "./utils/criteria-operator-validation";
22
24
  import { parseQueryValue } from "./utils/helpers";
23
25
 
@@ -278,7 +280,7 @@ export class Criteria<T = any> {
278
280
  static fromObject<T>(
279
281
  obj: {
280
282
  filters?: TypedFilter<T>[];
281
- orders?: Order[];
283
+ orders?: TypedOrder<T>[];
282
284
  pagination?: Pagination;
283
285
  search?: { fields: FieldPath<T>[]; value: string };
284
286
  },
@@ -459,10 +461,15 @@ export class Criteria<T = any> {
459
461
  }
460
462
 
461
463
  private validateOperator(operator: FilterOperator, value: any): void {
462
- if (value !== undefined && !isValidOperatorForType(value, operator)) {
463
- const validOps = getValidOperatorsForType(value);
464
+ const sanitizedValue = sanitizeFieldValue(value, operator);
465
+
466
+ if (
467
+ sanitizedValue !== undefined &&
468
+ !isValidOperatorForType(sanitizedValue, operator)
469
+ ) {
470
+ const validOps = getValidOperatorsForType(sanitizedValue);
464
471
  throw new InvalidCriteriaError(
465
- `Operator "${operator}" is not valid for type "${typeof value}". Valid operators: ${validOps.join(
472
+ `Operator "${operator}" is not valid for type "${typeof sanitizedValue}". Valid operators: ${validOps.join(
466
473
  ", "
467
474
  )}`,
468
475
  operator
package/src/crypto.ts ADDED
@@ -0,0 +1,31 @@
1
+ const customCrypto = {
2
+ randomUUID: () => {
3
+ if (
4
+ typeof globalThis !== "undefined" &&
5
+ globalThis?.crypto &&
6
+ typeof globalThis.crypto.randomUUID === "function"
7
+ ) {
8
+ return globalThis.crypto.randomUUID();
9
+ }
10
+
11
+ const hexChars = "0123456789abcdef";
12
+ let uuid = "";
13
+
14
+ for (let i = 0; i < 36; i++) {
15
+ if (i === 8 || i === 13 || i === 18 || i === 23) {
16
+ uuid += "-";
17
+ } else if (i === 14) {
18
+ uuid += "4";
19
+ } else if (i === 19) {
20
+ uuid += hexChars.charAt(Math.floor(Math.random() * 4) + 8);
21
+ } else {
22
+ uuid += hexChars.charAt(Math.floor(Math.random() * 16));
23
+ }
24
+ }
25
+
26
+ return uuid;
27
+ },
28
+ };
29
+
30
+ export const UUID = customCrypto.randomUUID;
31
+ export default UUID;
@@ -1,7 +1,3 @@
1
- // ============================================================================
2
- // Domain Events - Event-Driven Architecture Support
3
- // ============================================================================
4
-
5
1
  import { IDomainEvent } from ".";
6
2
  import { Id } from "./id";
7
3
 
@@ -0,0 +1,146 @@
1
+ import {
2
+ Operation,
3
+ CreateOperation,
4
+ UpdateOperation,
5
+ DeleteOperation,
6
+ } from "./types/change-tracker";
7
+
8
+ /**
9
+ * Represents the changes filtered for a specific entity.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const changes = user.getChanges();
14
+ * const postChanges = changes.for('Post');
15
+ *
16
+ * if (postChanges.hasCreates()) {
17
+ * console.log('Created posts:', postChanges.creates);
18
+ * }
19
+ *
20
+ * if (postChanges.hasUpdates()) {
21
+ * postChanges.updates.forEach(({ entity, changed }) => {
22
+ * console.log(`Post ${entity.id} has changed:`, changed);
23
+ * });
24
+ * }
25
+ * ```
26
+ */
27
+ export class EntityChanges<T = any> {
28
+ constructor(private readonly operations: Operation<T>[]) {}
29
+
30
+ /**
31
+ * Returns all created entities
32
+ */
33
+ get creates(): T[] {
34
+ return this.operations
35
+ .filter((op): op is CreateOperation<T> => op.type === "create")
36
+ .map((op) => op.data);
37
+ }
38
+
39
+ /**
40
+ * Returns all updated entities with their changed fields
41
+ */
42
+ get updates(): Array<{ entity: T; changed: Record<string, any> }> {
43
+ return this.operations
44
+ .filter((op): op is UpdateOperation<T> => op.type === "update")
45
+ .map((op) => ({
46
+ entity: op.data,
47
+ changed: op.changedFields,
48
+ }));
49
+ }
50
+
51
+ /**
52
+ * Returns all deleted entities
53
+ */
54
+ get deletes(): T[] {
55
+ return this.operations
56
+ .filter((op): op is DeleteOperation<T> => op.type === "delete")
57
+ .map((op) => op.data);
58
+ }
59
+
60
+ /**
61
+ * Returns the IDs of the created entities
62
+ */
63
+ get createIds(): string[] {
64
+ return this.operations
65
+ .filter((op): op is CreateOperation<T> => op.type === "create")
66
+ .map((op) => this.extractId(op.data))
67
+ .filter((id): id is string => id !== undefined);
68
+ }
69
+
70
+ /**
71
+ * Returns the IDs of the updated entities
72
+ */
73
+ get updateIds(): string[] {
74
+ return this.operations
75
+ .filter((op): op is UpdateOperation<T> => op.type === "update")
76
+ .map((op) => op.id);
77
+ }
78
+
79
+ /**
80
+ * Returns the IDs of the deleted entities
81
+ */
82
+ get deleteIds(): string[] {
83
+ return this.operations
84
+ .filter((op): op is DeleteOperation<T> => op.type === "delete")
85
+ .map((op) => op.id);
86
+ }
87
+
88
+ /**
89
+ * Checks if there are any creates
90
+ */
91
+ hasCreates(): boolean {
92
+ return this.creates.length > 0;
93
+ }
94
+
95
+ /**
96
+ * Checks if there are any updates
97
+ */
98
+ hasUpdates(): boolean {
99
+ return this.updates.length > 0;
100
+ }
101
+
102
+ /**
103
+ * Checks if there are any deletes
104
+ */
105
+ hasDeletes(): boolean {
106
+ return this.deletes.length > 0;
107
+ }
108
+
109
+ /**
110
+ * Checks if there is any change
111
+ */
112
+ hasChanges(): boolean {
113
+ return this.operations.length > 0;
114
+ }
115
+
116
+ /**
117
+ * Checks if it is empty (no changes)
118
+ */
119
+ isEmpty(): boolean {
120
+ return this.operations.length === 0;
121
+ }
122
+
123
+ /**
124
+ * Returns the total number of operations
125
+ */
126
+ get count(): number {
127
+ return this.operations.length;
128
+ }
129
+
130
+ /**
131
+ * Returns the raw operations (for advanced use cases)
132
+ */
133
+ get rawOperations(): Operation<T>[] {
134
+ return [...this.operations];
135
+ }
136
+
137
+ /**
138
+ * Extracts the ID from an entity
139
+ */
140
+ private extractId(entity: any): string | undefined {
141
+ if (!entity) return undefined;
142
+ if (entity.id?.value) return entity.id.value;
143
+ if (typeof entity.id === "string") return entity.id;
144
+ return undefined;
145
+ }
146
+ }
@@ -0,0 +1,255 @@
1
+ import { Entity } from "./entity";
2
+ import { ValueObject } from "./value-object";
3
+ import { Id } from "./id";
4
+
5
+ /**
6
+ * Mapping schema for a domain entity.
7
+ */
8
+ export interface EntitySchema {
9
+ /** Entity name in the domain (e.g., 'User', 'Post') */
10
+ entity: string;
11
+ /** Table name in the database (e.g., 'users', 'blog_posts') */
12
+ table: string;
13
+ /**
14
+ * Field mapping: domain → database.
15
+ * Only include fields with different names.
16
+ * @example { email: 'user_email', createdAt: 'created_at' }
17
+ */
18
+ fields?: Record<string, string>;
19
+ /**
20
+ * FK configuration for parent relation.
21
+ */
22
+ parentFk?: {
23
+ /** Name of the FK field in the database (e.g., 'author_id') */
24
+ field: string;
25
+ /** Name of the parent entity (e.g., 'User') */
26
+ parentEntity: string;
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Result of entity mapping.
32
+ */
33
+ export interface MappedEntityData {
34
+ [key: string]: any;
35
+ }
36
+
37
+ /**
38
+ * Registry for mapping domain entities to database tables and fields.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const registry = new EntitySchemaRegistry()
43
+ * .register({
44
+ * entity: 'User',
45
+ * table: 'users',
46
+ * fields: { email: 'user_email', name: 'user_name' },
47
+ * })
48
+ * .register({
49
+ * entity: 'Post',
50
+ * table: 'blog_posts',
51
+ * fields: { content: 'post_content' },
52
+ * parentFk: { field: 'author_id', parentEntity: 'User' },
53
+ * });
54
+ *
55
+ * const table = registry.getTable('Post'); // 'blog_posts'
56
+ * const mapped = registry.mapFields('User', { email: 'test@test.com' });
57
+ * // { user_email: 'test@test.com' }
58
+ * ```
59
+ */
60
+ export class EntitySchemaRegistry {
61
+ private schemas = new Map<string, EntitySchema>();
62
+
63
+ /**
64
+ * Registers an entity schema.
65
+ * @param schema - Schema to be registered.
66
+ * @returns this (for chaining)
67
+ */
68
+ register(schema: EntitySchema): this {
69
+ if (this.schemas.has(schema.entity)) {
70
+ console.warn(
71
+ `EntitySchemaRegistry: Schema for '${schema.entity}' is being overwritten`
72
+ );
73
+ }
74
+ this.schemas.set(schema.entity, schema);
75
+ return this;
76
+ }
77
+
78
+ /**
79
+ * Registers multiple schemas at once.
80
+ * @param schemas - Array of schemas.
81
+ * @returns this (for chaining)
82
+ */
83
+ registerAll(schemas: EntitySchema[]): this {
84
+ schemas.forEach((schema) => this.register(schema));
85
+ return this;
86
+ }
87
+
88
+ /**
89
+ * Gets the schema of an entity.
90
+ * @param entity - Entity name.
91
+ * @throws Error if the entity is not registered.
92
+ */
93
+ getSchema(entity: string): EntitySchema {
94
+ const schema = this.schemas.get(entity);
95
+ if (!schema) {
96
+ throw new Error(
97
+ `EntitySchemaRegistry: No schema registered for entity '${entity}'. ` +
98
+ `Available entities: ${Array.from(this.schemas.keys()).join(", ") || "none"}`
99
+ );
100
+ }
101
+ return schema;
102
+ }
103
+
104
+ /**
105
+ * Checks if an entity is registered.
106
+ * @param entity - Entity name.
107
+ */
108
+ has(entity: string): boolean {
109
+ return this.schemas.has(entity);
110
+ }
111
+
112
+ /**
113
+ * Gets the table name for an entity.
114
+ * @param entity - Entity name.
115
+ */
116
+ getTable(entity: string): string {
117
+ return this.getSchema(entity).table;
118
+ }
119
+
120
+ /**
121
+ * Gets the field mapping for an entity.
122
+ * @param entity - Entity name.
123
+ */
124
+ getFieldsMap(entity: string): Record<string, string> {
125
+ return this.getSchema(entity).fields || {};
126
+ }
127
+
128
+ /**
129
+ * Maps a domain field name to the database field name.
130
+ * @param entity - Entity name.
131
+ * @param fieldName - Domain field name.
132
+ */
133
+ mapFieldName(entity: string, fieldName: string): string {
134
+ const fields = this.getFieldsMap(entity);
135
+ return fields[fieldName] ?? fieldName;
136
+ }
137
+
138
+ /**
139
+ * Maps fields of a domain object to database field names.
140
+ * Ignores values that are Entity, ValueObject, or Arrays.
141
+ *
142
+ * @param entity - Entity name.
143
+ * @param data - Data to be mapped.
144
+ */
145
+ mapFields(entity: string, data: Record<string, any>): MappedEntityData {
146
+ const fields = this.getFieldsMap(entity);
147
+ const result: MappedEntityData = {};
148
+ for (const [key, value] of Object.entries(data)) {
149
+ if (this.isEntityOrCollection(value)) continue;
150
+ const mappedKey = fields[key] ?? key;
151
+ result[mappedKey] = this.normalizeValue(value);
152
+ }
153
+ return result;
154
+ }
155
+
156
+ /**
157
+ * Maps a complete domain entity to database data.
158
+ * Used for CREATE operations.
159
+ *
160
+ * @param entity - Entity name.
161
+ * @param domainEntity - Domain entity instance.
162
+ */
163
+ mapEntity(
164
+ entity: string,
165
+ domainEntity: Entity<any> | ValueObject<any>
166
+ ): MappedEntityData {
167
+ const fields = this.getFieldsMap(entity);
168
+ const result: MappedEntityData = {};
169
+ const hasId = (domainEntity as any).id;
170
+ if (hasId) {
171
+ const idValue = hasId.value ?? hasId;
172
+ result["id"] = idValue;
173
+ }
174
+ const props = (domainEntity as any).props || domainEntity;
175
+ for (const [key, value] of Object.entries(props)) {
176
+ if (key === "id") continue;
177
+ if (this.isEntityOrCollection(value)) continue;
178
+ const mappedKey = fields[key] ?? key;
179
+ result[mappedKey] = this.normalizeValue(value);
180
+ }
181
+ return result;
182
+ }
183
+
184
+ /**
185
+ * Gets the FK object to relate with the parent.
186
+ *
187
+ * @param entity - Entity name.
188
+ * @param parentId - Parent ID.
189
+ * @returns Object with the FK field or null if there is no parent.
190
+ */
191
+ getParentFk(entity: string, parentId: string): Record<string, string> | null {
192
+ const schema = this.getSchema(entity);
193
+ if (!schema.parentFk) return null;
194
+ return { [schema.parentFk.field]: parentId };
195
+ }
196
+
197
+ /**
198
+ * Gets the name of the parent entity.
199
+ * @param entity - Entity name.
200
+ */
201
+ getParentEntity(entity: string): string | null {
202
+ const schema = this.getSchema(entity);
203
+ return schema.parentFk?.parentEntity ?? null;
204
+ }
205
+
206
+ /**
207
+ * Gets the FK field name.
208
+ * @param entity - Entity name.
209
+ */
210
+ getParentFkField(entity: string): string | null {
211
+ const schema = this.getSchema(entity);
212
+ return schema.parentFk?.field ?? null;
213
+ }
214
+
215
+ /**
216
+ * Lists all registered entities.
217
+ */
218
+ getRegisteredEntities(): string[] {
219
+ return Array.from(this.schemas.keys());
220
+ }
221
+
222
+ /**
223
+ * Clears all registered schemas.
224
+ */
225
+ clear(): void {
226
+ this.schemas.clear();
227
+ }
228
+
229
+ /**
230
+ * Checks if a value is Entity, ValueObject, or Array.
231
+ */
232
+ private isEntityOrCollection(value: any): boolean {
233
+ if (value === null || value === undefined) return false;
234
+ if (Array.isArray(value)) return true;
235
+ if (value instanceof Entity) return true;
236
+ if (value instanceof ValueObject) return true;
237
+ if (typeof value === 'object' && value.id && typeof value.id === 'object' && 'value' in value.id) {
238
+ return true;
239
+ }
240
+ return false;
241
+ }
242
+
243
+ /**
244
+ * Normalizes a value for persistence.
245
+ */
246
+ private normalizeValue(value: any): any {
247
+ if (value === null || value === undefined) return value;
248
+ if (value instanceof Id) return value.value;
249
+ if (value instanceof Date) return value;
250
+ if (typeof value === "object" && "value" in value) {
251
+ return value.value;
252
+ }
253
+ return value;
254
+ }
255
+ }
package/src/entity.ts CHANGED
@@ -1,16 +1,5 @@
1
- // ============================================================================
2
- // Entity & Aggregate Classes
3
- // ============================================================================
4
-
5
1
  import { BaseEntity } from "./base-entity";
6
2
  import { BaseProps } from "./types";
7
3
 
8
- /**
9
- * Entity - Has identity and lifecycle, but is not an aggregate root
10
- */
11
4
  export class Entity<T extends BaseProps> extends BaseEntity<T> {}
12
-
13
- /**
14
- * Aggregate - Aggregate root that manages a consistency boundary
15
- */
16
5
  export class Aggregate<T extends BaseProps> extends BaseEntity<T> {}
package/src/id.ts CHANGED
@@ -2,42 +2,33 @@
2
2
  // Id Class - Smart Identity Management
3
3
  // ============================================================================
4
4
 
5
- function randomUUID(): string {
6
- // If we are in the browser, use the browser's crypto API
7
- // @ts-expect-error - window.crypto is not defined in the browser
8
- if (typeof window !== "undefined" && window.crypto) {
9
- // @ts-expect-error - window.crypto is not defined in the browser
10
- return window.crypto.randomUUID();
11
- }
12
- // If we are in the server, use the crypto library
13
- // eslint-disable-next-line @typescript-eslint/no-require-imports
14
- const crypto = require("crypto");
15
-
16
- return crypto.randomUUID();
17
- }
5
+ import UUID from "./crypto";
18
6
 
19
7
  export class Id {
20
8
  private readonly _value: string;
21
9
  private readonly _isNew: boolean;
22
10
 
11
+
23
12
  /**
24
13
  * Create a new Id
25
14
  * @param value - Optional existing ID value. If not provided, generates a new UUID.
26
- *
27
- * @example
28
- * // New entity (generates UUID)
29
- * const newId = new Id();
30
- * newuser.isNew() // true
31
- *
32
- * // Existing entity (uses provided ID)
33
- * const existingId = new Id("550e8400-e29b-41d4-a716-446655440000");
34
- * existinguser.isNew() // false
35
- */
36
- constructor(value?: string) {
15
+ *
16
+ * @example
17
+ * // New entity (generates UUID)
18
+ * const newId = new Id();
19
+ * newuser.isNew() // true
20
+ *
21
+ * // Existing entity (uses provided ID)
22
+ * const existingId = new Id("550e8400-e29b-41d4-a716-446655440000");
23
+ * existinguser.isNew() // false
24
+ */
25
+ constructor(value: string, isNew?: boolean);
26
+ constructor(value?: string);
27
+ constructor(value?: string, isNew?: boolean) {
37
28
  if (value !== undefined) {
38
29
  // ID was provided - this is an existing entity
39
30
  this._value = value;
40
- this._isNew = false;
31
+ this._isNew = isNew ?? false;
41
32
  } else {
42
33
  // No ID provided - generate new one, this is a new entity
43
34
  this._value = this.generateUUID();
@@ -88,7 +79,7 @@ export class Id {
88
79
  */
89
80
  private generateUUID(): string {
90
81
  // Simple UUID v4 implementation
91
- return randomUUID();
82
+ return UUID();
92
83
  }
93
84
 
94
85
  /**
package/src/index.ts CHANGED
@@ -1,28 +1,16 @@
1
- // ============================================================================
2
- // Rich Domain Library - Main Exports
3
- // ============================================================================
4
-
5
- // Core Classes
6
- export { Id } from "./id";
7
- export { Entity, Aggregate } from "./entity";
8
- export { ValueObject } from "./value-object";
9
- export { Mapper } from "./mapper";
10
-
11
1
  export * from "./validation-error";
12
-
13
- // Domain Events
14
2
  export * from "./domain-event";
15
3
  export * from "./domain-event-bus";
16
4
  export * from "./exceptions";
17
-
18
- // Criteria
19
5
  export * from "./criteria";
20
6
  export * from "./paginated-result";
21
-
22
- // Repository
23
7
  export * from "./repository";
24
-
25
- // Types
8
+ export { Id } from "./id";
9
+ export { Entity, Aggregate } from "./entity";
10
+ export { ValueObject } from "./value-object";
11
+ export { Mapper } from "./mapper";
12
+ export { EntitySchemaRegistry } from "./entity-schema-registry";
13
+ export { AggregateChanges } from "./aggregate-changes";
26
14
  export {
27
15
  DomainEventHandler,
28
16
  EntityHooks,
@@ -40,7 +28,6 @@ export {
40
28
  Order,
41
29
  IUnitOfWork,
42
30
  IDomainEventHandler,
43
- EntityId,
44
31
  FieldPath,
45
32
  FilterOperator,
46
33
  Search,
@@ -54,3 +41,12 @@ export {
54
41
  ArrayOperators,
55
42
  CriteriaOptions,
56
43
  } from "./types";
44
+ export {
45
+ ARRAY_OPERATORS,
46
+ BOOLEAN_OPERATORS,
47
+ DATE_OPERATORS,
48
+ NUMBER_OPERATORS,
49
+ STRING_OPERATORS,
50
+ FILTER_OPERATORS,
51
+ } from "./constants";
52
+ export { isValidOperatorForType } from "./utils/criteria-operator-validation";