@woltz/rich-domain 1.2.1 → 1.2.4

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 (93) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/base-entity.d.ts +1 -1
  3. package/dist/base-entity.d.ts.map +1 -1
  4. package/dist/base-entity.js +13 -46
  5. package/dist/base-entity.js.map +1 -1
  6. package/dist/{history-tracker.d.ts → change-tracker.d.ts} +3 -3
  7. package/dist/change-tracker.d.ts.map +1 -0
  8. package/dist/{history-tracker.js → change-tracker.js} +11 -57
  9. package/dist/change-tracker.js.map +1 -0
  10. package/dist/constants.d.ts +7 -1
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +65 -0
  13. package/dist/constants.js.map +1 -1
  14. package/dist/criteria.d.ts.map +1 -1
  15. package/dist/criteria.js +6 -4
  16. package/dist/criteria.js.map +1 -1
  17. package/dist/domain-event.d.ts.map +1 -1
  18. package/dist/domain-event.js +0 -3
  19. package/dist/domain-event.js.map +1 -1
  20. package/dist/entity-changes.d.ts.map +1 -1
  21. package/dist/entity-changes.js +0 -4
  22. package/dist/entity-changes.js.map +1 -1
  23. package/dist/entity-schema-registry.d.ts.map +1 -1
  24. package/dist/entity-schema-registry.js +2 -8
  25. package/dist/entity-schema-registry.js.map +1 -1
  26. package/dist/entity.d.ts +0 -6
  27. package/dist/entity.d.ts.map +1 -1
  28. package/dist/entity.js +0 -9
  29. package/dist/entity.js.map +1 -1
  30. package/dist/index.d.ts +9 -6
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +8 -12
  33. package/dist/index.js.map +1 -1
  34. package/dist/paginated-result.d.ts.map +1 -1
  35. package/dist/paginated-result.js +0 -15
  36. package/dist/paginated-result.js.map +1 -1
  37. package/dist/repository/base-repository.d.ts +1 -1
  38. package/dist/repository/base-repository.d.ts.map +1 -1
  39. package/dist/repository/index.d.ts.map +1 -1
  40. package/dist/repository/index.js +0 -6
  41. package/dist/repository/index.js.map +1 -1
  42. package/dist/repository/unit-of-work.d.ts.map +1 -1
  43. package/dist/repository/unit-of-work.js +0 -3
  44. package/dist/repository/unit-of-work.js.map +1 -1
  45. package/dist/types/change-tracker.d.ts +10 -0
  46. package/dist/types/change-tracker.d.ts.map +1 -1
  47. package/dist/types/domain.d.ts +4 -6
  48. package/dist/types/domain.d.ts.map +1 -1
  49. package/dist/types/index.d.ts +1 -1
  50. package/dist/types/index.d.ts.map +1 -1
  51. package/dist/types/index.js +1 -1
  52. package/dist/types/index.js.map +1 -1
  53. package/dist/utils/criteria-operator-validation.d.ts +1 -0
  54. package/dist/utils/criteria-operator-validation.d.ts.map +1 -1
  55. package/dist/utils/criteria-operator-validation.js +39 -17
  56. package/dist/utils/criteria-operator-validation.js.map +1 -1
  57. package/dist/validation-error.d.ts.map +1 -1
  58. package/dist/validation-error.js +1 -3
  59. package/dist/validation-error.js.map +1 -1
  60. package/dist/value-object.d.ts +1 -1
  61. package/dist/value-object.d.ts.map +1 -1
  62. package/dist/value-object.js +0 -1
  63. package/dist/value-object.js.map +1 -1
  64. package/package.json +1 -1
  65. package/src/base-entity.ts +13 -56
  66. package/src/{history-tracker.ts → change-tracker.ts} +23 -73
  67. package/src/constants.ts +75 -1
  68. package/src/criteria.ts +9 -3
  69. package/src/domain-event.ts +0 -4
  70. package/src/entity-changes.ts +0 -5
  71. package/src/entity-schema-registry.ts +2 -22
  72. package/src/entity.ts +0 -11
  73. package/src/index.ts +15 -20
  74. package/src/paginated-result.ts +0 -21
  75. package/src/repository/base-repository.ts +1 -1
  76. package/src/repository/index.ts +0 -9
  77. package/src/repository/unit-of-work.ts +0 -4
  78. package/src/types/change-tracker.ts +16 -4
  79. package/src/types/domain.ts +4 -8
  80. package/src/types/index.ts +1 -1
  81. package/src/utils/criteria-operator-validation.ts +57 -19
  82. package/src/validation-error.ts +1 -3
  83. package/src/value-object.ts +1 -2
  84. package/tests/depth/deep-tracking.test.ts +554 -0
  85. package/tests/history-tracker.spec.ts +1 -1
  86. package/tests/load-test/data.json +248187 -247017
  87. package/dist/history-tracker.d.ts.map +0 -1
  88. package/dist/history-tracker.js.map +0 -1
  89. package/dist/types/history-tracker.d.ts +0 -47
  90. package/dist/types/history-tracker.d.ts.map +0 -1
  91. package/dist/types/history-tracker.js +0 -2
  92. package/dist/types/history-tracker.js.map +0 -1
  93. package/src/types/history-tracker.ts +0 -58
@@ -13,10 +13,9 @@ import {
13
13
  import { DomainEventBus } from "./domain-event-bus";
14
14
  import { DEFAULT_VALIDATION_CONFIG } from "./constants";
15
15
  import { DomainError } from "./exceptions";
16
- import { HistoryTracker } from "./history-tracker";
16
+ import { ChangeTracker } from "./change-tracker";
17
17
  import { AggregateChanges } from "./aggregate-changes";
18
18
 
19
- // Helper to get static properties from constructor
20
19
  function getStaticProperty<T>(
21
20
  instance: any,
22
21
  propertyName: string
@@ -26,7 +25,7 @@ function getStaticProperty<T>(
26
25
 
27
26
  export abstract class BaseEntity<T extends BaseProps> {
28
27
  private _props: T;
29
- private tracker: HistoryTracker;
28
+ private tracker: ChangeTracker;
30
29
  private proxiedProps: T;
31
30
  private snapshot: T | null = null;
32
31
  private validationConfig: Required<ValidationConfig>;
@@ -34,12 +33,10 @@ export abstract class BaseEntity<T extends BaseProps> {
34
33
  private entitySchema?: StandardSchema<T>;
35
34
  private domainEvents: IDomainEvent[] = [];
36
35
 
37
- // Static properties that subclasses can override
38
36
  protected static validation?: EntityValidation<any>;
39
37
  protected static hooks?: EntityHooks<any, any>;
40
38
 
41
39
  constructor(props: Omit<T, "id"> & { id?: Id }) {
42
- // Get static configuration from subclass
43
40
  const validation = getStaticProperty<EntityValidation<T>>(
44
41
  this,
45
42
  "validation"
@@ -59,46 +56,34 @@ export abstract class BaseEntity<T extends BaseProps> {
59
56
 
60
57
  let finalProps = { ...props } as T;
61
58
 
62
- // Generate ID if not provided
63
59
  if (!finalProps.id) {
64
60
  finalProps.id = new Id();
65
61
  }
66
62
 
67
- // Validate schema on creation
68
63
  if (this.entitySchema && this.validationConfig.onCreate) {
69
64
  this.validateProps(finalProps);
70
65
  }
71
66
 
72
67
  this._props = finalProps;
68
+ this.tracker = new ChangeTracker(this._props, this.constructor.name);
73
69
 
74
- // Initialize tracker and proxy
75
- this.tracker = new HistoryTracker(this._props, this.constructor.name);
76
-
77
- // Setup validation on update BEFORE creating proxy
78
70
  if (this.validationConfig.onUpdate) {
79
71
  this.setupUpdateValidation();
80
72
  }
81
73
 
82
74
  this.proxiedProps = this.tracker.createProxy();
83
75
 
84
- // Execute rules (custom validations)
85
76
  if (hooks?.rules) {
86
77
  hooks.rules(this as any);
87
78
  }
88
79
 
89
- // Hook onCreate
90
80
  if (hooks?.onCreate) {
91
81
  hooks.onCreate(this as any);
92
82
  }
93
83
 
94
- // Take initial snapshot for onBeforeUpdate
95
84
  this.takeSnapshot();
96
85
  }
97
86
 
98
- // ============================================================================
99
- // Validation
100
- // ============================================================================
101
-
102
87
  private validateProps(props: T): void {
103
88
  if (!this.entitySchema) return;
104
89
 
@@ -144,31 +129,27 @@ export abstract class BaseEntity<T extends BaseProps> {
144
129
 
145
130
  /**
146
131
  * Setup validation that runs on every property change.
147
- * Uses the HistoryTracker's onChangeValidator callback.
132
+ * Uses the ChangeTracker's onChangeValidator callback.
148
133
  */
149
134
  private setupUpdateValidation(): void {
150
135
  const self = this;
151
136
 
152
137
  this.tracker.setOnChangeValidator((path, oldValue, newValue) => {
153
- // Temporarily apply the change to validate
154
138
  const originalValue = self._props[path as keyof T];
155
139
  (self._props as any)[path] = newValue;
156
140
 
157
141
  try {
158
- // Check onBeforeUpdate hook
159
142
  if (self.entityHooks?.onBeforeUpdate && self.snapshot) {
160
143
  const shouldContinue = self.entityHooks.onBeforeUpdate(
161
144
  self as any,
162
145
  self.snapshot
163
146
  );
164
147
  if (!shouldContinue) {
165
- // Revert change
166
148
  (self._props as any)[path] = originalValue;
167
149
  return false;
168
150
  }
169
151
  }
170
152
 
171
- // Validate with schema
172
153
  if (self.entitySchema) {
173
154
  const result = self.entitySchema["~standard"].validate(self._props);
174
155
 
@@ -188,7 +169,6 @@ export abstract class BaseEntity<T extends BaseProps> {
188
169
  }))
189
170
  );
190
171
 
191
- // Revert change before throwing
192
172
  (self._props as any)[path] = originalValue;
193
173
 
194
174
  if (self.validationConfig.throwOnError) {
@@ -200,12 +180,10 @@ export abstract class BaseEntity<T extends BaseProps> {
200
180
  }
201
181
  }
202
182
 
203
- // Execute rules after schema validation
204
183
  if (self.entityHooks?.rules) {
205
184
  try {
206
185
  self.entityHooks.rules(self as any);
207
186
  } catch (error) {
208
- // Revert change before throwing
209
187
  (self._props as any)[path] = originalValue;
210
188
 
211
189
  if (self.validationConfig.throwOnError) {
@@ -217,14 +195,9 @@ export abstract class BaseEntity<T extends BaseProps> {
217
195
  }
218
196
  }
219
197
 
220
- // Revert for now - the actual set will happen in the proxy
221
198
  (self._props as any)[path] = originalValue;
222
-
223
- // Update snapshot after successful validation
224
- // Note: snapshot is updated after the change is applied
225
199
  return true;
226
200
  } catch (error) {
227
- // Revert on any error
228
201
  (self._props as any)[path] = originalValue;
229
202
  throw error;
230
203
  }
@@ -269,22 +242,22 @@ export abstract class BaseEntity<T extends BaseProps> {
269
242
  }
270
243
 
271
244
  if (obj.constructor === Object) {
272
- const cloned: any = {};
273
- for (const key in obj) {
274
- if (obj.hasOwnProperty(key)) {
275
- cloned[key] = this.deepCloneProps(obj[key], seen);
245
+ try {
246
+ return structuredClone(obj);
247
+ } catch {
248
+ const cloned: any = {};
249
+ for (const key in obj) {
250
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
251
+ cloned[key] = this.deepCloneProps(obj[key], seen);
252
+ }
276
253
  }
254
+ return cloned;
277
255
  }
278
- return cloned;
279
256
  }
280
257
 
281
258
  return obj;
282
259
  }
283
260
 
284
- // ============================================================================
285
- // Identity
286
- // ============================================================================
287
-
288
261
  get id(): Id {
289
262
  return this._props.id;
290
263
  }
@@ -316,10 +289,6 @@ export abstract class BaseEntity<T extends BaseProps> {
316
289
  return false;
317
290
  }
318
291
 
319
- // ============================================================================
320
- // Props Access
321
- // ============================================================================
322
-
323
292
  public get props(): T {
324
293
  return this.proxiedProps;
325
294
  }
@@ -338,10 +307,6 @@ export abstract class BaseEntity<T extends BaseProps> {
338
307
  return (this as any)._validationError;
339
308
  }
340
309
 
341
- // ============================================================================
342
- // Change Tracking
343
- // ============================================================================
344
-
345
310
  /**
346
311
  * Returns all detected changes as AggregateChanges.
347
312
  *
@@ -375,10 +340,6 @@ export abstract class BaseEntity<T extends BaseProps> {
375
340
  this.takeSnapshot();
376
341
  }
377
342
 
378
- // ============================================================================
379
- // Domain Events
380
- // ============================================================================
381
-
382
343
  /**
383
344
  * Add a domain event to this entity
384
345
  */
@@ -415,10 +376,6 @@ export abstract class BaseEntity<T extends BaseProps> {
415
376
  return this.domainEvents.length > 0;
416
377
  }
417
378
 
418
- // ============================================================================
419
- // Serialization
420
- // ============================================================================
421
-
422
379
  toJson(): DeepJsonResult<T> {
423
380
  return this.deepToJson(this._props) as DeepJsonResult<T>;
424
381
  }
@@ -27,7 +27,7 @@ export type OnChangeValidator = (
27
27
  * - Generates AggregateChanges for persistence
28
28
  * - Supports validation on change via onChangeValidator
29
29
  */
30
- export class HistoryTracker {
30
+ export class ChangeTracker {
31
31
  private history: HistoryEntry[] = [];
32
32
  private originalValues: Map<string, any> = new Map();
33
33
  private trackedArrays: Map<string, ArrayState> = new Map();
@@ -41,7 +41,7 @@ export class HistoryTracker {
41
41
  private depth: number = 0,
42
42
  private parentId?: string,
43
43
  private parentEntity?: string,
44
- private rootTracker?: HistoryTracker
44
+ private rootTracker?: ChangeTracker
45
45
  ) {
46
46
  if (!rootTracker) {
47
47
  this.rootTracker = this;
@@ -60,10 +60,6 @@ export class HistoryTracker {
60
60
  this.getRootTracker().onChangeValidator = validator;
61
61
  }
62
62
 
63
- // ============================================================================
64
- // Initial State Capture
65
- // ============================================================================
66
-
67
63
  private captureInitialState(): void {
68
64
  if (this.depth > 0) return;
69
65
  this.captureEntityState(this.target, this.rootEntityName, "", 0);
@@ -153,10 +149,6 @@ export class HistoryTracker {
153
149
  });
154
150
  }
155
151
 
156
- // ============================================================================
157
- // Proxy Creation
158
- // ============================================================================
159
-
160
152
  createProxy(): any {
161
153
  const handler: ProxyHandler<any> = {
162
154
  get: (target, prop, receiver) => {
@@ -177,7 +169,7 @@ export class HistoryTracker {
177
169
  }
178
170
 
179
171
  if (this.isEntityOrVO(value)) {
180
- const nestedTracker = new HistoryTracker(
172
+ const nestedTracker = new ChangeTracker(
181
173
  value,
182
174
  this.getEntityName(value),
183
175
  currentPath,
@@ -200,7 +192,6 @@ export class HistoryTracker {
200
192
  return true;
201
193
  }
202
194
 
203
- // Call validator before making the change
204
195
  const rootTracker = this.getRootTracker();
205
196
  if (rootTracker.onChangeValidator) {
206
197
  try {
@@ -210,21 +201,17 @@ export class HistoryTracker {
210
201
  newValue
211
202
  );
212
203
  if (result === false) {
213
- // Validator rejected the change
214
- return true; // Return true to not throw, but don't apply change
204
+ return true;
215
205
  }
216
206
  } catch (error) {
217
- // Validator threw an error - propagate it
218
207
  throw error;
219
208
  }
220
209
  }
221
210
 
222
- // Store original value
223
211
  if (!rootTracker.originalValues.has(currentPath)) {
224
212
  rootTracker.originalValues.set(currentPath, oldValue);
225
213
  }
226
214
 
227
- // Record in history
228
215
  rootTracker.history.push({
229
216
  path: currentPath,
230
217
  previousValue: oldValue,
@@ -234,7 +221,6 @@ export class HistoryTracker {
234
221
 
235
222
  const result = Reflect.set(target, prop, newValue, receiver);
236
223
 
237
- // Handle special cases
238
224
  if (Array.isArray(newValue)) {
239
225
  this.handleArrayAssignment(currentPath, oldValue);
240
226
  } else if (this.isEntityOrVO(newValue) || this.isEntityOrVO(oldValue)) {
@@ -284,14 +270,12 @@ export class HistoryTracker {
284
270
  return function (...args: any[]) {
285
271
  const oldArray = target.slice();
286
272
 
287
- // Call validator before array mutation
288
273
  if (rootTracker.onChangeValidator) {
289
274
  try {
290
- const result = rootTracker.onChangeValidator(
291
- path,
292
- oldArray,
293
- [...oldArray, ...args] // Preview of change
294
- );
275
+ const result = rootTracker.onChangeValidator(path, oldArray, [
276
+ ...oldArray,
277
+ ...args,
278
+ ]);
295
279
  if (result === false) {
296
280
  return undefined;
297
281
  }
@@ -317,7 +301,7 @@ export class HistoryTracker {
317
301
 
318
302
  if (!isNaN(Number(prop)) && tracker.isEntityOrVO(value)) {
319
303
  const nestedPath = `${path}[${String(prop)}]`;
320
- const nestedTracker = new HistoryTracker(
304
+ const nestedTracker = new ChangeTracker(
321
305
  value,
322
306
  tracker.getEntityName(value),
323
307
  nestedPath,
@@ -336,7 +320,6 @@ export class HistoryTracker {
336
320
  if (!isNaN(Number(prop))) {
337
321
  const oldArray = target.slice();
338
322
 
339
- // Call validator before array item change
340
323
  if (rootTracker.onChangeValidator) {
341
324
  try {
342
325
  const result = rootTracker.onChangeValidator(
@@ -368,10 +351,6 @@ export class HistoryTracker {
368
351
  });
369
352
  }
370
353
 
371
- // ============================================================================
372
- // getChanges() - Main Method
373
- // ============================================================================
374
-
375
354
  /**
376
355
  * Returns all detected changes as AggregateChanges.
377
356
  */
@@ -388,7 +367,7 @@ export class HistoryTracker {
388
367
 
389
368
  private analyzeRootChanges(
390
369
  changes: AggregateChanges<any>,
391
- rootTracker: HistoryTracker
370
+ rootTracker: ChangeTracker
392
371
  ): void {
393
372
  const changedFields: Record<string, any> = {};
394
373
  let hasChanges = false;
@@ -420,7 +399,7 @@ export class HistoryTracker {
420
399
 
421
400
  private analyzeCollectionChanges(
422
401
  changes: AggregateChanges<any>,
423
- rootTracker: HistoryTracker
402
+ rootTracker: ChangeTracker
424
403
  ): void {
425
404
  const allTrackedArrays = new Map<string, ArrayState>();
426
405
  const processedArrays = new Set<any>();
@@ -456,7 +435,6 @@ export class HistoryTracker {
456
435
  const itemEntityName = this.getEntityName(item);
457
436
  changes.addCreate(itemEntityName, item, depth, parentId, parentEntity);
458
437
 
459
- // Recursively mark nested items as created
460
438
  this.markNestedItemsAsCreated(item, depth, changes);
461
439
  }
462
440
 
@@ -482,7 +460,6 @@ export class HistoryTracker {
482
460
  const deleteId = id || key!;
483
461
  changes.addDelete(itemEntityName, deleteId, item, depth);
484
462
 
485
- // Recursively mark nested items as deleted using ORIGINAL state
486
463
  this.markNestedItemsAsDeleted(item, depth, changes, rootTracker);
487
464
  }
488
465
  }
@@ -508,7 +485,6 @@ export class HistoryTracker {
508
485
  if (propName === "id") continue;
509
486
 
510
487
  if (Array.isArray(value)) {
511
- // Process all items in the array
512
488
  for (const nestedItem of value) {
513
489
  if (this.isEntityOrVO(nestedItem)) {
514
490
  const nestedId = this.getEntityId(nestedItem);
@@ -523,8 +499,11 @@ export class HistoryTracker {
523
499
  this.getEntityName(item)
524
500
  );
525
501
 
526
- // Recursively process nested items
527
- this.markNestedItemsAsCreated(nestedItem, parentDepth + 1, changes);
502
+ this.markNestedItemsAsCreated(
503
+ nestedItem,
504
+ parentDepth + 1,
505
+ changes
506
+ );
528
507
  }
529
508
  }
530
509
  }
@@ -540,22 +519,16 @@ export class HistoryTracker {
540
519
  item: any,
541
520
  parentDepth: number,
542
521
  changes: AggregateChanges<any>,
543
- rootTracker: HistoryTracker
522
+ rootTracker: ChangeTracker
544
523
  ): void {
545
524
  if (!item || typeof item !== "object") return;
546
525
 
547
- // Get the ID to look up the original state
548
526
  const itemId = this.getEntityId(item);
549
527
  if (!itemId) return;
550
528
 
551
- // Look through all tracked arrays to find nested items
552
529
  for (const [, arrayState] of rootTracker.trackedArrays) {
553
- // Check if this array belongs to our deleted item
554
530
  if (arrayState.metadata.parentId === itemId) {
555
- // Use the CLONED (original) state to get the items
556
- // Note: cloned items are JSON objects, not Entity/VO instances
557
531
  for (const nestedItem of arrayState.cloned) {
558
- // Cloned items are JSON objects with an 'id' property
559
532
  const id =
560
533
  typeof nestedItem === "object" && nestedItem !== null
561
534
  ? nestedItem.id
@@ -564,7 +537,6 @@ export class HistoryTracker {
564
537
  const entityName = arrayState.metadata.entityName;
565
538
  changes.addDelete(entityName, id, nestedItem, parentDepth + 1);
566
539
 
567
- // Recursively process this item's nested arrays
568
540
  this.markNestedJsonItemAsDeleted(
569
541
  id,
570
542
  parentDepth + 1,
@@ -585,12 +557,10 @@ export class HistoryTracker {
585
557
  itemId: string,
586
558
  parentDepth: number,
587
559
  changes: AggregateChanges<any>,
588
- rootTracker: HistoryTracker
560
+ rootTracker: ChangeTracker
589
561
  ): void {
590
- // Look through all tracked arrays to find nested items of this parent
591
562
  for (const [, arrayState] of rootTracker.trackedArrays) {
592
563
  if (arrayState.metadata.parentId === itemId) {
593
- // Process all items in this nested array
594
564
  for (const nestedJsonItem of arrayState.cloned) {
595
565
  if (typeof nestedJsonItem !== "object" || nestedJsonItem === null)
596
566
  continue;
@@ -606,7 +576,6 @@ export class HistoryTracker {
606
576
  parentDepth + 1
607
577
  );
608
578
 
609
- // Recursively process further nesting
610
579
  this.markNestedJsonItemAsDeleted(
611
580
  nestedId,
612
581
  parentDepth + 1,
@@ -614,7 +583,6 @@ export class HistoryTracker {
614
583
  rootTracker
615
584
  );
616
585
  } else {
617
- // Value object - try to extract identity key
618
586
  const key = this.extractIdentityKeyFromJson(
619
587
  nestedJsonItem,
620
588
  arrayState.original
@@ -640,20 +608,16 @@ export class HistoryTracker {
640
608
  jsonItem: any,
641
609
  originalArray: any[]
642
610
  ): string | undefined {
643
- // Try to find the original ValueObject to get its identity key
644
611
  for (const originalItem of originalArray) {
645
612
  if (this.isEntityOrVO(originalItem)) {
646
613
  const originalJson = this.deepClone(originalItem);
647
- // Check if this matches our JSON item (rough comparison)
648
614
  if (JSON.stringify(originalJson) === JSON.stringify(jsonItem)) {
649
- // Found the matching original item - extract its identity key
650
615
  const key = this.getItemKey(originalItem);
651
616
  if (key) return key;
652
617
  }
653
618
  }
654
619
  }
655
620
 
656
- // Fallback: if it has an id, use that
657
621
  if (jsonItem.id) return jsonItem.id;
658
622
 
659
623
  return undefined;
@@ -692,7 +656,7 @@ export class HistoryTracker {
692
656
 
693
657
  private analyzeEntityChanges(
694
658
  changes: AggregateChanges<any>,
695
- rootTracker: HistoryTracker
659
+ rootTracker: ChangeTracker
696
660
  ): void {
697
661
  for (const [path, trackedItem] of rootTracker.trackedEntities) {
698
662
  if (path === "root") continue;
@@ -720,7 +684,6 @@ export class HistoryTracker {
720
684
  case "deleted":
721
685
  const id = this.getEntityId(originalValue);
722
686
  if (id) {
723
- // Use originalEntity instead of originalValue to preserve entity instance
724
687
  changes.addDelete(entityName, id, originalEntity, depth);
725
688
  }
726
689
  break;
@@ -728,7 +691,6 @@ export class HistoryTracker {
728
691
  case "replaced":
729
692
  const oldId = this.getEntityId(originalValue);
730
693
  if (oldId) {
731
- // Use originalEntity instead of originalValue to preserve entity instance
732
694
  changes.addDelete(entityName, oldId, originalEntity, depth);
733
695
  }
734
696
  changes.addCreate(
@@ -762,10 +724,6 @@ export class HistoryTracker {
762
724
  }
763
725
  }
764
726
 
765
- // ============================================================================
766
- // Change Detection Helpers
767
- // ============================================================================
768
-
769
727
  private detectEntityChangeState(
770
728
  previous: any,
771
729
  current: any
@@ -864,10 +822,6 @@ export class HistoryTracker {
864
822
  return changes;
865
823
  }
866
824
 
867
- // ============================================================================
868
- // Internal Handlers
869
- // ============================================================================
870
-
871
825
  private handleArrayAssignment(path: string, oldValue: any): void {
872
826
  const rootTracker = this.getRootTracker();
873
827
 
@@ -892,7 +846,6 @@ export class HistoryTracker {
892
846
  const existingTracked = rootTracker.trackedEntities.get(path);
893
847
 
894
848
  rootTracker.trackedEntities.set(path, {
895
- // Preserve original entity, or use oldValue if this is the first change
896
849
  entity: existingTracked?.entity || oldValue,
897
850
  metadata: {
898
851
  entityName,
@@ -901,16 +854,11 @@ export class HistoryTracker {
901
854
  parentEntity: this.rootEntityName,
902
855
  path,
903
856
  },
904
- // Preserve original state
905
857
  originalState: existingTracked?.originalState,
906
858
  });
907
859
  }
908
860
 
909
- // ============================================================================
910
- // Utility Methods
911
- // ============================================================================
912
-
913
- private getRootTracker(): HistoryTracker {
861
+ private getRootTracker(): ChangeTracker {
914
862
  return this.rootTracker || this;
915
863
  }
916
864
 
@@ -938,7 +886,9 @@ export class HistoryTracker {
938
886
 
939
887
  for (const part of parts) {
940
888
  if (current === null || current === undefined) return undefined;
941
- current = current[part];
889
+
890
+ const propsToAccess = current.props || current;
891
+ current = propsToAccess[part];
942
892
  }
943
893
 
944
894
  return current;
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
@@ -19,6 +19,7 @@ import {
19
19
  isValidOperatorForType,
20
20
  getValidOperatorsForType,
21
21
  isOperator,
22
+ sanitizeFieldValue,
22
23
  } from "./utils/criteria-operator-validation";
23
24
  import { parseQueryValue } from "./utils/helpers";
24
25
 
@@ -460,10 +461,15 @@ export class Criteria<T = any> {
460
461
  }
461
462
 
462
463
  private validateOperator(operator: FilterOperator, value: any): void {
463
- if (value !== undefined && !isValidOperatorForType(value, operator)) {
464
- 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);
465
471
  throw new InvalidCriteriaError(
466
- `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(
467
473
  ", "
468
474
  )}`,
469
475
  operator
@@ -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
 
@@ -1,8 +1,3 @@
1
- // ============================================================================
2
- // Entity Changes
3
- // Changes filtered by entity type
4
- // ============================================================================
5
-
6
1
  import {
7
2
  Operation,
8
3
  CreateOperation,