@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.
- package/CHANGELOG.md +56 -0
- package/dist/base-entity.d.ts +1 -1
- package/dist/base-entity.d.ts.map +1 -1
- package/dist/base-entity.js +13 -46
- package/dist/base-entity.js.map +1 -1
- package/dist/{history-tracker.d.ts → change-tracker.d.ts} +3 -3
- package/dist/change-tracker.d.ts.map +1 -0
- package/dist/{history-tracker.js → change-tracker.js} +11 -57
- package/dist/change-tracker.js.map +1 -0
- package/dist/constants.d.ts +7 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +65 -0
- package/dist/constants.js.map +1 -1
- package/dist/criteria.d.ts.map +1 -1
- package/dist/criteria.js +6 -4
- package/dist/criteria.js.map +1 -1
- package/dist/domain-event.d.ts.map +1 -1
- package/dist/domain-event.js +0 -3
- package/dist/domain-event.js.map +1 -1
- package/dist/entity-changes.d.ts.map +1 -1
- package/dist/entity-changes.js +0 -4
- package/dist/entity-changes.js.map +1 -1
- package/dist/entity-schema-registry.d.ts.map +1 -1
- package/dist/entity-schema-registry.js +2 -8
- package/dist/entity-schema-registry.js.map +1 -1
- package/dist/entity.d.ts +0 -6
- package/dist/entity.d.ts.map +1 -1
- package/dist/entity.js +0 -9
- package/dist/entity.js.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -12
- package/dist/index.js.map +1 -1
- package/dist/paginated-result.d.ts.map +1 -1
- package/dist/paginated-result.js +0 -15
- package/dist/paginated-result.js.map +1 -1
- package/dist/repository/base-repository.d.ts +1 -1
- package/dist/repository/base-repository.d.ts.map +1 -1
- package/dist/repository/index.d.ts.map +1 -1
- package/dist/repository/index.js +0 -6
- package/dist/repository/index.js.map +1 -1
- package/dist/repository/unit-of-work.d.ts.map +1 -1
- package/dist/repository/unit-of-work.js +0 -3
- package/dist/repository/unit-of-work.js.map +1 -1
- package/dist/types/change-tracker.d.ts +10 -0
- package/dist/types/change-tracker.d.ts.map +1 -1
- package/dist/types/domain.d.ts +4 -6
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/criteria-operator-validation.d.ts +1 -0
- package/dist/utils/criteria-operator-validation.d.ts.map +1 -1
- package/dist/utils/criteria-operator-validation.js +39 -17
- package/dist/utils/criteria-operator-validation.js.map +1 -1
- package/dist/validation-error.d.ts.map +1 -1
- package/dist/validation-error.js +1 -3
- package/dist/validation-error.js.map +1 -1
- package/dist/value-object.d.ts +1 -1
- package/dist/value-object.d.ts.map +1 -1
- package/dist/value-object.js +0 -1
- package/dist/value-object.js.map +1 -1
- package/package.json +1 -1
- package/src/base-entity.ts +13 -56
- package/src/{history-tracker.ts → change-tracker.ts} +23 -73
- package/src/constants.ts +75 -1
- package/src/criteria.ts +9 -3
- package/src/domain-event.ts +0 -4
- package/src/entity-changes.ts +0 -5
- package/src/entity-schema-registry.ts +2 -22
- package/src/entity.ts +0 -11
- package/src/index.ts +15 -20
- package/src/paginated-result.ts +0 -21
- package/src/repository/base-repository.ts +1 -1
- package/src/repository/index.ts +0 -9
- package/src/repository/unit-of-work.ts +0 -4
- package/src/types/change-tracker.ts +16 -4
- package/src/types/domain.ts +4 -8
- package/src/types/index.ts +1 -1
- package/src/utils/criteria-operator-validation.ts +57 -19
- package/src/validation-error.ts +1 -3
- package/src/value-object.ts +1 -2
- package/tests/depth/deep-tracking.test.ts +554 -0
- package/tests/history-tracker.spec.ts +1 -1
- package/tests/load-test/data.json +248187 -247017
- package/dist/history-tracker.d.ts.map +0 -1
- package/dist/history-tracker.js.map +0 -1
- package/dist/types/history-tracker.d.ts +0 -47
- package/dist/types/history-tracker.d.ts.map +0 -1
- package/dist/types/history-tracker.js +0 -2
- package/dist/types/history-tracker.js.map +0 -1
- package/src/types/history-tracker.ts +0 -58
package/src/base-entity.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
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?:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
527
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
464
|
-
|
|
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
|
|
472
|
+
`Operator "${operator}" is not valid for type "${typeof sanitizedValue}". Valid operators: ${validOps.join(
|
|
467
473
|
", "
|
|
468
474
|
)}`,
|
|
469
475
|
operator
|
package/src/domain-event.ts
CHANGED
|
@@ -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
|
|
package/src/entity-changes.ts
CHANGED