@woltz/rich-domain 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +33 -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 +117 -86
  9. package/dist/base-entity.js.map +1 -1
  10. package/dist/criteria.d.ts +3 -3
  11. package/dist/criteria.d.ts.map +1 -1
  12. package/dist/criteria.js.map +1 -1
  13. package/dist/crypto.d.ts +3 -0
  14. package/dist/crypto.d.ts.map +1 -0
  15. package/dist/crypto.js +29 -0
  16. package/dist/crypto.js.map +1 -0
  17. package/dist/entity-changes.d.ts +84 -0
  18. package/dist/entity-changes.d.ts.map +1 -0
  19. package/dist/entity-changes.js +135 -0
  20. package/dist/entity-changes.js.map +1 -0
  21. package/dist/entity-schema-registry.d.ts +148 -0
  22. package/dist/entity-schema-registry.d.ts.map +1 -0
  23. package/dist/entity-schema-registry.js +219 -0
  24. package/dist/entity-schema-registry.js.map +1 -0
  25. package/dist/history-tracker.d.ts +97 -0
  26. package/dist/history-tracker.d.ts.map +1 -0
  27. package/dist/history-tracker.js +805 -0
  28. package/dist/history-tracker.js.map +1 -0
  29. package/dist/id.d.ts +11 -10
  30. package/dist/id.d.ts.map +1 -1
  31. package/dist/id.js +4 -28
  32. package/dist/id.js.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/mapper.d.ts +1 -1
  38. package/dist/mapper.d.ts.map +1 -1
  39. package/dist/mapper.js.map +1 -1
  40. package/dist/repository/base-repository.d.ts +6 -32
  41. package/dist/repository/base-repository.d.ts.map +1 -1
  42. package/dist/repository/base-repository.js +0 -27
  43. package/dist/repository/base-repository.js.map +1 -1
  44. package/dist/repository/unit-of-work.d.ts +0 -25
  45. package/dist/repository/unit-of-work.d.ts.map +1 -1
  46. package/dist/repository/unit-of-work.js +0 -25
  47. package/dist/repository/unit-of-work.js.map +1 -1
  48. package/dist/types/change-tracker.d.ts +186 -0
  49. package/dist/types/change-tracker.d.ts.map +1 -0
  50. package/dist/types/change-tracker.js +2 -0
  51. package/dist/types/change-tracker.js.map +1 -0
  52. package/dist/types/criteria.d.ts +5 -1
  53. package/dist/types/criteria.d.ts.map +1 -1
  54. package/dist/types/history-tracker.d.ts +11 -0
  55. package/dist/types/history-tracker.d.ts.map +1 -1
  56. package/dist/types/utils.d.ts +0 -1
  57. package/dist/types/utils.d.ts.map +1 -1
  58. package/dist/validation-error.d.ts.map +1 -1
  59. package/dist/validation-error.js +0 -3
  60. package/dist/validation-error.js.map +1 -1
  61. package/dist/value-object.d.ts +57 -8
  62. package/dist/value-object.d.ts.map +1 -1
  63. package/dist/value-object.js +49 -21
  64. package/dist/value-object.js.map +1 -1
  65. package/package.json +2 -1
  66. package/src/aggregate-changes.ts +335 -0
  67. package/src/base-entity.ts +140 -100
  68. package/src/criteria.ts +2 -1
  69. package/src/crypto.ts +31 -0
  70. package/src/entity-changes.ts +151 -0
  71. package/src/entity-schema-registry.ts +275 -0
  72. package/src/history-tracker.ts +1114 -0
  73. package/src/id.ts +17 -26
  74. package/src/index.ts +1 -0
  75. package/src/mapper.ts +4 -1
  76. package/src/repository/base-repository.ts +6 -37
  77. package/src/repository/unit-of-work.ts +0 -25
  78. package/src/types/change-tracker.ts +221 -0
  79. package/src/types/criteria.ts +6 -1
  80. package/src/types/history-tracker.ts +13 -0
  81. package/src/types/utils.ts +0 -9
  82. package/src/validation-error.ts +0 -4
  83. package/src/value-object.ts +84 -23
  84. package/tests/aggregate-changes.test.ts +284 -0
  85. package/tests/criteria.test.ts +122 -161
  86. package/tests/entity-equality.test.ts +38 -61
  87. package/tests/entity-schema-registry.test.ts +382 -0
  88. package/tests/entity-validation.test.ts +7 -94
  89. package/tests/history-tracker.spec.ts +349 -617
  90. package/tests/id.test.ts +41 -44
  91. package/tests/load-test/data.json +346041 -0
  92. package/tests/load-test/entities.ts +97 -0
  93. package/tests/load-test/generate-data.ts +81 -0
  94. package/tests/load-test/lead-to-domain.mapper.ts +24 -0
  95. package/tests/load-test/load.test.ts +38 -0
  96. package/tests/repository.test.ts +30 -54
  97. package/tests/to-json.test.ts +14 -18
  98. package/tests/utils.ts +138 -102
  99. package/tests/value-objects.test.ts +57 -29
  100. package/dist/deep-proxy.d.ts +0 -36
  101. package/dist/deep-proxy.d.ts.map +0 -1
  102. package/dist/deep-proxy.js +0 -384
  103. package/dist/deep-proxy.js.map +0 -1
  104. package/src/deep-proxy.ts +0 -447
  105. package/tests/entity.test.ts +0 -33
@@ -0,0 +1,335 @@
1
+ import {
2
+ Operation,
3
+ CreateOperation,
4
+ UpdateOperation,
5
+ DeleteOperation,
6
+ BatchOperations,
7
+ BatchCreateItem,
8
+ BatchUpdateItem,
9
+ } from "./types/change-tracker";
10
+ import { EntityChanges } from "./entity-changes";
11
+
12
+ /**
13
+ * Manages and organizes the changes of an Aggregate.
14
+ *
15
+ * Responsibilities:
16
+ * - Stores all operations (create, update, delete)
17
+ * - Orders operations respecting FK dependencies
18
+ * - Groups operations by entity for batch execution
19
+ * - Provides query and iteration methods
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // Define an entity map for type-safe operations
24
+ * type UserEntities = {
25
+ * User: User;
26
+ * Post: Post;
27
+ * Comment: Comment;
28
+ * };
29
+ *
30
+ * // Getting changes with types
31
+ * const changes = user.getChanges<UserEntities>();
32
+ *
33
+ * // Filtering by entity with autocompletion
34
+ * const postChanges = changes.for('Post'); // 'Post' autocompletes
35
+ * postChanges.creates.forEach(post => {
36
+ * console.log(post.title); // 'post' is typed as Post
37
+ * });
38
+ * ```
39
+ */
40
+ export class AggregateChanges<TEntityMap = Record<string, any>> {
41
+ private ops: Operation[] = [];
42
+
43
+ constructor(operations: Operation[] = []) {
44
+ this.ops = [...operations];
45
+ }
46
+
47
+ /**
48
+ * Adds a create operation.
49
+ */
50
+ addCreate<T>(
51
+ entity: string,
52
+ data: T,
53
+ depth: number,
54
+ parentId?: string,
55
+ parentEntity?: string
56
+ ): void {
57
+ this.ops.push({
58
+ type: "create",
59
+ entity,
60
+ data,
61
+ depth,
62
+ parentId,
63
+ parentEntity,
64
+ } as CreateOperation<T>);
65
+ }
66
+
67
+ /**
68
+ * Adds an update operation.
69
+ */
70
+ addUpdate<T>(
71
+ entity: string,
72
+ id: string,
73
+ data: T,
74
+ changedFields: Record<string, any>,
75
+ depth: number
76
+ ): void {
77
+ this.ops.push({
78
+ type: "update",
79
+ entity,
80
+ id,
81
+ data,
82
+ changedFields,
83
+ depth,
84
+ } as UpdateOperation<T>);
85
+ }
86
+
87
+ /**
88
+ * Adds a delete operation.
89
+ */
90
+ addDelete<T>(entity: string, id: string, data: T, depth: number): void {
91
+ this.ops.push({
92
+ type: "delete",
93
+ entity,
94
+ id,
95
+ data,
96
+ depth,
97
+ } as DeleteOperation<T>);
98
+ }
99
+
100
+ /**
101
+ * Returns all create operations, sorted by ascending depth (root → leaf).
102
+ */
103
+ creates(): CreateOperation[] {
104
+ return this.ops
105
+ .filter((op): op is CreateOperation => op.type === "create")
106
+ .sort((a, b) => a.depth - b.depth);
107
+ }
108
+
109
+ /**
110
+ * Returns all update operations.
111
+ */
112
+ updates(): UpdateOperation[] {
113
+ return this.ops.filter((op): op is UpdateOperation => op.type === "update");
114
+ }
115
+
116
+ /**
117
+ * Returns all delete operations, sorted by descending depth (leaf → root).
118
+ */
119
+ deletes(): DeleteOperation[] {
120
+ return this.ops
121
+ .filter((op): op is DeleteOperation => op.type === "delete")
122
+ .sort((a, b) => b.depth - a.depth);
123
+ }
124
+
125
+ /**
126
+ * Iterator that returns operations in the correct execution order:
127
+ * 1. Deletes (leaf → root)
128
+ * 2. Creates (root → leaf)
129
+ * 3. Updates
130
+ */
131
+ *operations(): Generator<Operation> {
132
+ yield* this.deletes();
133
+ yield* this.creates();
134
+ yield* this.updates();
135
+ }
136
+
137
+ /**
138
+ * Returns all operations as an array in execution order.
139
+ */
140
+ toArray(): Operation[] {
141
+ return [...this.operations()];
142
+ }
143
+
144
+ /**
145
+ * Converts the changes into BatchOperations for optimized execution.
146
+ *
147
+ * Groups operations by entity and sorts by depth:
148
+ * - Deletes: depth DESC (leaf → root)
149
+ * - Creates: depth ASC (root → leaf)
150
+ * - Updates: grouped by entity
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * const batch = changes.toBatchOperations();
155
+ *
156
+ * // Run deletes
157
+ * for (const del of batch.deletes) {
158
+ * await tx[del.entity].deleteMany({ where: { id: { in: del.ids } } });
159
+ * }
160
+ *
161
+ * // Run creates
162
+ * for (const create of batch.creates) {
163
+ * await tx[create.entity].createMany({ data: create.items });
164
+ * }
165
+ * ```
166
+ */
167
+ toBatchOperations(): BatchOperations {
168
+ return {
169
+ deletes: this.groupDeletes(),
170
+ creates: this.groupCreates(),
171
+ updates: this.groupUpdates(),
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Groups deletes by entity, sorted by descending depth.
177
+ */
178
+ private groupDeletes(): BatchOperations["deletes"] {
179
+ const deleteOps = this.deletes();
180
+ const grouped = new Map<string, { depth: number; ids: string[] }>();
181
+
182
+ for (const op of deleteOps) {
183
+ if (!grouped.has(op.entity)) {
184
+ grouped.set(op.entity, { depth: op.depth, ids: [] });
185
+ }
186
+ grouped.get(op.entity)!.ids.push(op.id);
187
+ }
188
+
189
+ return Array.from(grouped.entries())
190
+ .map(([entity, { depth, ids }]) => ({ entity, depth, ids }))
191
+ .sort((a, b) => b.depth - a.depth);
192
+ }
193
+
194
+ /**
195
+ * Groups creates by entity, sorted by ascending depth.
196
+ */
197
+ private groupCreates(): BatchOperations["creates"] {
198
+ const createOps = this.creates();
199
+ const grouped = new Map<
200
+ string,
201
+ { depth: number; items: BatchCreateItem[] }
202
+ >();
203
+
204
+ for (const op of createOps) {
205
+ if (!grouped.has(op.entity)) {
206
+ grouped.set(op.entity, { depth: op.depth, items: [] });
207
+ }
208
+ grouped.get(op.entity)!.items.push({
209
+ data: op.data,
210
+ parentId: op.parentId,
211
+ });
212
+ }
213
+
214
+ return Array.from(grouped.entries())
215
+ .map(([entity, { depth, items }]) => ({ entity, depth, items }))
216
+ .sort((a, b) => a.depth - b.depth);
217
+ }
218
+
219
+ /**
220
+ * Groups updates by entity.
221
+ */
222
+ private groupUpdates(): BatchOperations["updates"] {
223
+ const updateOps = this.updates();
224
+ const grouped = new Map<string, BatchUpdateItem[]>();
225
+
226
+ for (const op of updateOps) {
227
+ if (!grouped.has(op.entity)) {
228
+ grouped.set(op.entity, []);
229
+ }
230
+ grouped.get(op.entity)!.push({
231
+ id: op.id,
232
+ changedFields: op.changedFields,
233
+ });
234
+ }
235
+
236
+ return Array.from(grouped.entries()).map(([entity, items]) => ({
237
+ entity,
238
+ items,
239
+ }));
240
+ }
241
+
242
+ /**
243
+ * Filters changes by entity name.
244
+ *
245
+ * @param entityName - Name of the entity (e.g., 'Post', 'Comment')
246
+ * @returns EntityChanges containing only the operations for this entity
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * const postChanges = changes.for('Post');
251
+ *
252
+ * if (postChanges.hasCreates()) {
253
+ * postChanges.creates.forEach(post => {
254
+ * console.log('New post:', post.title);
255
+ * });
256
+ * }
257
+ * ```
258
+ */
259
+ for<K extends keyof TEntityMap>(entityName: K): EntityChanges<TEntityMap[K]> {
260
+ const filtered = this.ops.filter((op) => op.entity === entityName);
261
+ return new EntityChanges<TEntityMap[K]>(filtered);
262
+ }
263
+
264
+ /**
265
+ * Checks if there are create operations.
266
+ */
267
+ hasCreates(): boolean {
268
+ return this.ops.some((op) => op.type === "create");
269
+ }
270
+
271
+ /**
272
+ * Checks if there are update operations.
273
+ */
274
+ hasUpdates(): boolean {
275
+ return this.ops.some((op) => op.type === "update");
276
+ }
277
+
278
+ /**
279
+ * Checks if there are delete operations.
280
+ */
281
+ hasDeletes(): boolean {
282
+ return this.ops.some((op) => op.type === "delete");
283
+ }
284
+
285
+ /**
286
+ * Checks if there are any operations.
287
+ */
288
+ hasChanges(): boolean {
289
+ return this.ops.length > 0;
290
+ }
291
+
292
+ /**
293
+ * Checks if there are no operations.
294
+ */
295
+ isEmpty(): boolean {
296
+ return this.ops.length === 0;
297
+ }
298
+
299
+ /**
300
+ * Returns the total number of operations.
301
+ */
302
+ get count(): number {
303
+ return this.ops.length;
304
+ }
305
+
306
+ /**
307
+ * Returns the raw operations (for debug/testing).
308
+ */
309
+ get rawOperations(): Operation[] {
310
+ return [...this.ops];
311
+ }
312
+
313
+ /**
314
+ * Lists all entities that have changes.
315
+ */
316
+ getAffectedEntities(): string[] {
317
+ const entities = new Set<string>();
318
+ this.ops.forEach((op) => entities.add(op.entity));
319
+ return Array.from(entities);
320
+ }
321
+
322
+ /**
323
+ * Clears all operations.
324
+ */
325
+ clear(): void {
326
+ this.ops = [];
327
+ }
328
+
329
+ /**
330
+ * Creates a copy of the changes.
331
+ */
332
+ clone(): AggregateChanges<TEntityMap> {
333
+ return new AggregateChanges<TEntityMap>([...this.ops]);
334
+ }
335
+ }