@woltz/rich-domain 1.1.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 (121) hide show
  1. package/CHANGELOG.md +87 -21
  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 +31 -15
  11. package/dist/criteria.d.ts.map +1 -1
  12. package/dist/criteria.js +151 -61
  13. package/dist/criteria.js.map +1 -1
  14. package/dist/crypto.d.ts +3 -0
  15. package/dist/crypto.d.ts.map +1 -0
  16. package/dist/crypto.js +29 -0
  17. package/dist/crypto.js.map +1 -0
  18. package/dist/entity-changes.d.ts +84 -0
  19. package/dist/entity-changes.d.ts.map +1 -0
  20. package/dist/entity-changes.js +135 -0
  21. package/dist/entity-changes.js.map +1 -0
  22. package/dist/entity-schema-registry.d.ts +148 -0
  23. package/dist/entity-schema-registry.d.ts.map +1 -0
  24. package/dist/entity-schema-registry.js +219 -0
  25. package/dist/entity-schema-registry.js.map +1 -0
  26. package/dist/history-tracker.d.ts +97 -0
  27. package/dist/history-tracker.d.ts.map +1 -0
  28. package/dist/history-tracker.js +805 -0
  29. package/dist/history-tracker.js.map +1 -0
  30. package/dist/id.d.ts +11 -10
  31. package/dist/id.d.ts.map +1 -1
  32. package/dist/id.js +4 -28
  33. package/dist/id.js.map +1 -1
  34. package/dist/index.d.ts +2 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/mapper.d.ts +1 -1
  39. package/dist/mapper.d.ts.map +1 -1
  40. package/dist/mapper.js.map +1 -1
  41. package/dist/paginated-result.d.ts.map +1 -1
  42. package/dist/paginated-result.js +7 -6
  43. package/dist/paginated-result.js.map +1 -1
  44. package/dist/repository/base-repository.d.ts +6 -32
  45. package/dist/repository/base-repository.d.ts.map +1 -1
  46. package/dist/repository/base-repository.js +0 -27
  47. package/dist/repository/base-repository.js.map +1 -1
  48. package/dist/repository/unit-of-work.d.ts +0 -25
  49. package/dist/repository/unit-of-work.d.ts.map +1 -1
  50. package/dist/repository/unit-of-work.js +0 -25
  51. package/dist/repository/unit-of-work.js.map +1 -1
  52. package/dist/types/change-tracker.d.ts +186 -0
  53. package/dist/types/change-tracker.d.ts.map +1 -0
  54. package/dist/types/change-tracker.js +2 -0
  55. package/dist/types/change-tracker.js.map +1 -0
  56. package/dist/types/criteria.d.ts +31 -7
  57. package/dist/types/criteria.d.ts.map +1 -1
  58. package/dist/types/history-tracker.d.ts +11 -0
  59. package/dist/types/history-tracker.d.ts.map +1 -1
  60. package/dist/types/utils.d.ts +0 -1
  61. package/dist/types/utils.d.ts.map +1 -1
  62. package/dist/utils/criteria-operator-validation.d.ts +5 -0
  63. package/dist/utils/criteria-operator-validation.d.ts.map +1 -0
  64. package/dist/utils/criteria-operator-validation.js +143 -0
  65. package/dist/utils/criteria-operator-validation.js.map +1 -0
  66. package/dist/utils/helpers.d.ts +2 -0
  67. package/dist/utils/helpers.d.ts.map +1 -0
  68. package/dist/utils/helpers.js +10 -0
  69. package/dist/utils/helpers.js.map +1 -0
  70. package/dist/validation-error.d.ts.map +1 -1
  71. package/dist/validation-error.js +0 -3
  72. package/dist/validation-error.js.map +1 -1
  73. package/dist/value-object.d.ts +57 -8
  74. package/dist/value-object.d.ts.map +1 -1
  75. package/dist/value-object.js +49 -21
  76. package/dist/value-object.js.map +1 -1
  77. package/eslint.config.js +6 -0
  78. package/package.json +2 -1
  79. package/src/aggregate-changes.ts +335 -0
  80. package/src/base-entity.ts +140 -100
  81. package/src/criteria.ts +264 -82
  82. package/src/crypto.ts +31 -0
  83. package/src/entity-changes.ts +151 -0
  84. package/src/entity-schema-registry.ts +275 -0
  85. package/src/history-tracker.ts +1114 -0
  86. package/src/id.ts +17 -26
  87. package/src/index.ts +8 -2
  88. package/src/mapper.ts +4 -1
  89. package/src/paginated-result.ts +7 -8
  90. package/src/repository/base-repository.ts +6 -37
  91. package/src/repository/unit-of-work.ts +0 -25
  92. package/src/types/change-tracker.ts +221 -0
  93. package/src/types/criteria.ts +95 -17
  94. package/src/types/history-tracker.ts +13 -0
  95. package/src/types/utils.ts +0 -9
  96. package/src/utils/criteria-operator-validation.ts +171 -0
  97. package/src/utils/helpers.ts +6 -0
  98. package/src/validation-error.ts +0 -4
  99. package/src/value-object.ts +84 -23
  100. package/tests/aggregate-changes.test.ts +284 -0
  101. package/tests/criteria.test.ts +366 -90
  102. package/tests/entity-equality.test.ts +38 -61
  103. package/tests/entity-schema-registry.test.ts +382 -0
  104. package/tests/entity-validation.test.ts +7 -94
  105. package/tests/history-tracker.spec.ts +349 -617
  106. package/tests/id.test.ts +41 -44
  107. package/tests/load-test/data.json +346041 -0
  108. package/tests/load-test/entities.ts +97 -0
  109. package/tests/load-test/generate-data.ts +81 -0
  110. package/tests/load-test/lead-to-domain.mapper.ts +24 -0
  111. package/tests/load-test/load.test.ts +38 -0
  112. package/tests/repository.test.ts +30 -54
  113. package/tests/to-json.test.ts +14 -18
  114. package/tests/utils.ts +138 -102
  115. package/tests/value-objects.test.ts +57 -29
  116. package/dist/deep-proxy.d.ts +0 -36
  117. package/dist/deep-proxy.d.ts.map +0 -1
  118. package/dist/deep-proxy.js +0 -384
  119. package/dist/deep-proxy.js.map +0 -1
  120. package/src/deep-proxy.ts +0 -447
  121. package/tests/entity.test.ts +0 -33
@@ -1 +1 @@
1
- {"version":3,"file":"value-object.d.ts","sourceRoot":"","sources":["../src/value-object.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AACjC,OAAO,EACL,OAAO,EAGP,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAYjB,8BAAsB,WAAW,CAAC,CAAC;IACjC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,WAAW,CAAC,CAAkB;IACtC,OAAO,CAAC,YAAY,CAAC,CAAoB;IACzC,OAAO,CAAC,YAAY,CAAsB;IAG1C,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACpD,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAE/B,KAAK,EAAE,CAAC;IA2CpB,OAAO,CAAC,aAAa;IA4BrB,OAAO,CAAC,cAAc;IAmBtB;;OAEG;IACH,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAED;;OAEG;IACH,IAAI,gBAAgB,IAAI,eAAe,GAAG,SAAS,CAElD;IAED,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO;IAKtC;;OAEG;IACH,SAAS,CAAC,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAInD;;OAEG;IACH,oBAAoB,IAAI,YAAY,EAAE;IAItC;;OAEG;IACH,WAAW,IAAI,IAAI;IAInB;;OAEG;IACH,oBAAoB,IAAI,OAAO;IAI/B,MAAM,IAAI,CAAC;IAIX;;;OAGG;IACH,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;CAI3C"}
1
+ {"version":3,"file":"value-object.d.ts","sourceRoot":"","sources":["../src/value-object.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AACjC,OAAO,EACL,OAAO,EAGP,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAYjB;;GAEG;AACH,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;AAE7D,8BAAsB,WAAW,CAAC,CAAC;IACjC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,WAAW,CAAC,CAAkB;IACtC,OAAO,CAAC,YAAY,CAAC,CAAoB;IACzC,OAAO,CAAC,YAAY,CAAsB;IAE1C,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACpD,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAE3C;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAC;gBAE9C,KAAK,EAAE,CAAC;IAqCpB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,cAAc;IAgBtB;;OAEG;IACH,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAED;;OAEG;IACH,IAAI,gBAAgB,IAAI,eAAe,GAAG,SAAS,CAElD;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO;IAKtC;;;;;;;;;;;;;;OAcG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAiB/B;;OAEG;IACH,cAAc,IAAI,OAAO;IAOzB;;OAEG;IACH,MAAM,CAAC,wBAAwB,CAAC,CAAC,KAAK,qBAAqB,CAAC,CAAC,CAAC,GAAG,SAAS;IAI1E;;OAEG;IACH,SAAS,CAAC,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAInD;;OAEG;IACH,oBAAoB,IAAI,YAAY,EAAE;IAItC;;OAEG;IACH,WAAW,IAAI,IAAI;IAInB;;OAEG;IACH,oBAAoB,IAAI,OAAO;IAI/B,MAAM,IAAI,CAAC;IAIX;;;OAGG;IACH,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;CAI3C"}
@@ -1,6 +1,3 @@
1
- // ============================================================================
2
- // Value Object - Immutable Domain Objects
3
- // ============================================================================
4
1
  import { ValidationError } from "./validation-error";
5
2
  import { DEFAULT_VALIDATION_CONFIG } from "./constants";
6
3
  import { DomainError } from "./exceptions";
@@ -11,7 +8,6 @@ function getStaticProperty(instance, propertyName) {
11
8
  export class ValueObject {
12
9
  constructor(props) {
13
10
  this.domainEvents = [];
14
- // Get static configuration from subclass
15
11
  const validation = getStaticProperty(this, "validation");
16
12
  const hooks = getStaticProperty(this, "hooks");
17
13
  this.domainHooks = hooks;
@@ -23,19 +19,14 @@ export class ValueObject {
23
19
  ...validation?.config,
24
20
  };
25
21
  let finalProps = { ...props };
26
- // Validate schema on creation
27
22
  if (this.domainSchema && this.validationConfig.onCreate) {
28
23
  this.validateProps(finalProps);
29
24
  }
30
- // Set props (not frozen yet) so rules can access them
31
25
  this.props = finalProps;
32
- // Execute rules (custom validations) - after props is set but before freezing
33
26
  if (hooks?.rules) {
34
27
  hooks.rules(this);
35
28
  }
36
- // Now freeze the props for immutability
37
29
  Object.freeze(this.props);
38
- // Hook onCreate
39
30
  if (hooks?.onCreate) {
40
31
  hooks.onCreate(this);
41
32
  }
@@ -55,7 +46,6 @@ export class ValueObject {
55
46
  if (this.validationConfig.throwOnError) {
56
47
  throw validationError;
57
48
  }
58
- // If not throwing, store error for later retrieval
59
49
  this._validationError = validationError;
60
50
  }
61
51
  }
@@ -63,57 +53,95 @@ export class ValueObject {
63
53
  if (pathSegment === null || pathSegment === undefined) {
64
54
  return "";
65
55
  }
66
- // Handle PropertyKey (string | number | symbol)
67
56
  if (typeof pathSegment === "string" || typeof pathSegment === "number") {
68
57
  return String(pathSegment);
69
58
  }
70
59
  if (typeof pathSegment === "symbol") {
71
60
  return pathSegment.toString();
72
61
  }
73
- // Handle object with 'key' property (Zod's PathSegment)
74
62
  if (typeof pathSegment === "object" && "key" in pathSegment) {
75
63
  return String(pathSegment.key);
76
64
  }
77
- // Fallback
78
65
  return String(pathSegment);
79
66
  }
80
67
  /**
81
- * Check if value object has validation errors (when throwOnError is false)
68
+ * Returns true if the value object has validation errors (when throwOnError is false).
82
69
  */
83
70
  get hasValidationErrors() {
84
71
  return !!this._validationError;
85
72
  }
86
73
  /**
87
- * Get validation errors (when throwOnError is false)
74
+ * Returns the validation errors (when throwOnError is false).
88
75
  */
89
76
  get validationErrors() {
90
77
  return this._validationError;
91
78
  }
79
+ /**
80
+ * Compare this ValueObject with another for equality based on their properties.
81
+ */
92
82
  equals(other) {
93
83
  if (!other || !(other instanceof ValueObject))
94
84
  return false;
95
85
  return JSON.stringify(this.props) === JSON.stringify(other.props);
96
86
  }
97
87
  /**
98
- * Add a domain event to this value object
88
+ * Returns the identity key for this Value Object.
89
+ * Used for identification in collections when identityKey is set.
90
+ *
91
+ * @returns String with the identity key or null if not defined
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * const like = new Like({ postId: 'p1', userId: 'u1' });
96
+ * like.getIdentityKey(); // 'p1:u1'
97
+ *
98
+ * const tag = new TagReference({ tagId: 'tag-123' });
99
+ * tag.getIdentityKey(); // 'tag-123'
100
+ * ```
101
+ */
102
+ getIdentityKey() {
103
+ const keyDef = getStaticProperty(this, "identityKey");
104
+ if (!keyDef) {
105
+ return null;
106
+ }
107
+ if (Array.isArray(keyDef)) {
108
+ return keyDef.map((k) => String(this.props[k])).join(":");
109
+ }
110
+ return String(this.props[keyDef]);
111
+ }
112
+ /**
113
+ * Returns true if this Value Object has an identity key defined.
114
+ */
115
+ hasIdentityKey() {
116
+ return (getStaticProperty(this, "identityKey") !==
117
+ undefined);
118
+ }
119
+ /**
120
+ * Returns the identity key definition (if any).
121
+ */
122
+ static getIdentityKeyDefinition() {
123
+ return this.identityKey;
124
+ }
125
+ /**
126
+ * Adds a domain event to this value object.
99
127
  */
100
128
  addDomainEvent(event) {
101
129
  this.domainEvents.push(event);
102
130
  }
103
131
  /**
104
- * Get all uncommitted domain events
132
+ * Returns all uncommitted domain events.
105
133
  */
106
134
  getUncommittedEvents() {
107
135
  return [...this.domainEvents];
108
136
  }
109
137
  /**
110
- * Clear all domain events (call after publishing)
138
+ * Clears all domain events (call after publishing).
111
139
  */
112
140
  clearEvents() {
113
141
  this.domainEvents = [];
114
142
  }
115
143
  /**
116
- * Check if value object has uncommitted events
144
+ * Returns true if the value object has uncommitted events.
117
145
  */
118
146
  hasUncommittedEvents() {
119
147
  return this.domainEvents.length > 0;
@@ -122,8 +150,8 @@ export class ValueObject {
122
150
  return { ...this.props };
123
151
  }
124
152
  /**
125
- * Create a new ValueObject with updated properties
126
- * Since ValueObjects are immutable, this returns a new instance
153
+ * Creates a new ValueObject with updated properties.
154
+ * ValueObjects are immutable, so this returns a new instance.
127
155
  */
128
156
  clone(updates) {
129
157
  const Constructor = this.constructor;
@@ -1 +1 @@
1
- {"version":3,"file":"value-object.js","sourceRoot":"","sources":["../src/value-object.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"value-object.js","sourceRoot":"","sources":["../src/value-object.ts"],"names":[],"mappings":"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"}
package/eslint.config.js CHANGED
@@ -1,5 +1,10 @@
1
1
  import tsParser from "@typescript-eslint/parser";
2
2
  import tsPlugin from "@typescript-eslint/eslint-plugin";
3
+ import { dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
3
8
 
4
9
  export default [
5
10
  {
@@ -19,6 +24,7 @@ export default [
19
24
  ecmaVersion: 2020,
20
25
  sourceType: "module",
21
26
  project: "./tsconfig.json",
27
+ tsconfigRootDir: __dirname,
22
28
  },
23
29
  globals: {
24
30
  console: "readonly",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@woltz/rich-domain",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Rich Domain Library with Standard Schema validation support",
5
5
  "homepage": "https://github.com/tarcisioandrade/rich-domain",
6
6
  "type": "module",
@@ -41,6 +41,7 @@
41
41
  "typescript"
42
42
  ],
43
43
  "devDependencies": {
44
+ "@faker-js/faker": "^9.9.0",
44
45
  "zod": "^3.24.0"
45
46
  },
46
47
  "peerDependencies": {
@@ -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
+ }