@woltz/rich-domain 1.2.4 → 1.3.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 (94) hide show
  1. package/dist/aggregate-changes.d.ts +56 -14
  2. package/dist/aggregate-changes.d.ts.map +1 -1
  3. package/dist/aggregate-changes.js +103 -23
  4. package/dist/aggregate-changes.js.map +1 -1
  5. package/dist/base-entity.d.ts +1 -1
  6. package/dist/base-entity.d.ts.map +1 -1
  7. package/dist/base-entity.js +28 -13
  8. package/dist/base-entity.js.map +1 -1
  9. package/dist/change-tracker.d.ts +2 -1
  10. package/dist/change-tracker.d.ts.map +1 -1
  11. package/dist/change-tracker.js +61 -35
  12. package/dist/change-tracker.js.map +1 -1
  13. package/dist/criteria.d.ts +7 -15
  14. package/dist/criteria.d.ts.map +1 -1
  15. package/dist/criteria.js +105 -81
  16. package/dist/criteria.js.map +1 -1
  17. package/dist/domain-event-bus.js +4 -4
  18. package/dist/domain-event-bus.js.map +1 -1
  19. package/dist/domain-event.js +3 -0
  20. package/dist/domain-event.js.map +1 -1
  21. package/dist/entity-changes.js +1 -0
  22. package/dist/entity-changes.js.map +1 -1
  23. package/dist/entity-schema-registry.d.ts +137 -3
  24. package/dist/entity-schema-registry.d.ts.map +1 -1
  25. package/dist/entity-schema-registry.js +160 -7
  26. package/dist/entity-schema-registry.js.map +1 -1
  27. package/dist/exceptions.js +26 -1
  28. package/dist/exceptions.js.map +1 -1
  29. package/dist/id.js +2 -0
  30. package/dist/id.js.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/paginated-result.d.ts +4 -4
  34. package/dist/paginated-result.d.ts.map +1 -1
  35. package/dist/paginated-result.js +14 -19
  36. package/dist/paginated-result.js.map +1 -1
  37. package/dist/repository/unit-of-work.js +3 -7
  38. package/dist/repository/unit-of-work.js.map +1 -1
  39. package/dist/types/change-tracker.d.ts +30 -0
  40. package/dist/types/change-tracker.d.ts.map +1 -1
  41. package/dist/types/criteria.d.ts +1 -4
  42. package/dist/types/criteria.d.ts.map +1 -1
  43. package/dist/types/domain.d.ts +2 -1
  44. package/dist/types/domain.d.ts.map +1 -1
  45. package/dist/types/utils.d.ts +2 -2
  46. package/dist/utils/helpers.d.ts +1 -0
  47. package/dist/utils/helpers.d.ts.map +1 -1
  48. package/dist/utils/helpers.js +23 -0
  49. package/dist/utils/helpers.js.map +1 -1
  50. package/dist/validation-error.d.ts +15 -1
  51. package/dist/validation-error.d.ts.map +1 -1
  52. package/dist/validation-error.js +46 -3
  53. package/dist/validation-error.js.map +1 -1
  54. package/dist/value-object.d.ts +1 -1
  55. package/dist/value-object.d.ts.map +1 -1
  56. package/dist/value-object.js +30 -2
  57. package/dist/value-object.js.map +1 -1
  58. package/package.json +17 -3
  59. package/src/aggregate-changes.ts +133 -24
  60. package/src/base-entity.ts +22 -11
  61. package/src/change-tracker.ts +113 -54
  62. package/src/criteria.ts +151 -109
  63. package/src/entity-schema-registry.ts +256 -6
  64. package/src/index.ts +1 -1
  65. package/src/paginated-result.ts +21 -29
  66. package/src/types/change-tracker.ts +31 -0
  67. package/src/types/criteria.ts +1 -4
  68. package/src/types/domain.ts +2 -1
  69. package/src/types/utils.ts +2 -2
  70. package/src/utils/helpers.ts +28 -0
  71. package/src/validation-error.ts +54 -4
  72. package/src/value-object.ts +6 -1
  73. package/.versionrc.json +0 -21
  74. package/CHANGELOG.md +0 -163
  75. package/tests/aggregate-changes.test.ts +0 -284
  76. package/tests/criteria.test.ts +0 -716
  77. package/tests/depth/deep-tracking.test.ts +0 -554
  78. package/tests/domain-events.test.ts +0 -431
  79. package/tests/entity-equality.test.ts +0 -464
  80. package/tests/entity-schema-registry.test.ts +0 -382
  81. package/tests/entity-validation.test.ts +0 -252
  82. package/tests/history-tracker.spec.ts +0 -439
  83. package/tests/id.test.ts +0 -338
  84. package/tests/load-test/data.json +0 -347211
  85. package/tests/load-test/entities.ts +0 -97
  86. package/tests/load-test/generate-data.ts +0 -81
  87. package/tests/load-test/lead-to-domain.mapper.ts +0 -24
  88. package/tests/load-test/load.test.ts +0 -38
  89. package/tests/repository.test.ts +0 -635
  90. package/tests/to-json.test.ts +0 -99
  91. package/tests/utils.ts +0 -290
  92. package/tests/value-object-validation.test.ts +0 -219
  93. package/tests/value-objects.test.ts +0 -80
  94. package/tsconfig.json +0 -9
@@ -1,382 +0,0 @@
1
- // ============================================================================
2
- // Tests: EntitySchemaRegistry
3
- // ============================================================================
4
-
5
- import { EntitySchemaRegistry } from "../src/entity-schema-registry";
6
-
7
- describe("EntitySchemaRegistry", () => {
8
- let registry: EntitySchemaRegistry;
9
-
10
- beforeEach(() => {
11
- registry = new EntitySchemaRegistry();
12
- });
13
-
14
- describe("register", () => {
15
- it("should register a schema", () => {
16
- registry.register({
17
- entity: "User",
18
- table: "users",
19
- });
20
-
21
- expect(registry.has("User")).toBe(true);
22
- });
23
-
24
- it("should allow chaining", () => {
25
- const result = registry
26
- .register({ entity: "User", table: "users" })
27
- .register({ entity: "Post", table: "posts" });
28
-
29
- expect(result).toBe(registry);
30
- expect(registry.has("User")).toBe(true);
31
- expect(registry.has("Post")).toBe(true);
32
- });
33
-
34
- it("should register with fields mapping", () => {
35
- registry.register({
36
- entity: "User",
37
- table: "users",
38
- fields: {
39
- email: "user_email",
40
- name: "user_name",
41
- },
42
- });
43
-
44
- expect(registry.getFieldsMap("User")).toEqual({
45
- email: "user_email",
46
- name: "user_name",
47
- });
48
- });
49
-
50
- it("should register with parent FK", () => {
51
- registry.register({
52
- entity: "Post",
53
- table: "posts",
54
- parentFk: {
55
- field: "author_id",
56
- parentEntity: "User",
57
- },
58
- });
59
-
60
- expect(registry.getParentEntity("Post")).toBe("User");
61
- expect(registry.getParentFkField("Post")).toBe("author_id");
62
- });
63
- });
64
-
65
- describe("registerAll", () => {
66
- it("should register multiple schemas at once", () => {
67
- registry.registerAll([
68
- { entity: "User", table: "users" },
69
- { entity: "Post", table: "posts" },
70
- { entity: "Comment", table: "comments" },
71
- ]);
72
-
73
- expect(registry.getRegisteredEntities()).toEqual([
74
- "User",
75
- "Post",
76
- "Comment",
77
- ]);
78
- });
79
- });
80
-
81
- describe("getSchema", () => {
82
- it("should return registered schema", () => {
83
- registry.register({
84
- entity: "User",
85
- table: "users",
86
- fields: { email: "user_email" },
87
- });
88
-
89
- const schema = registry.getSchema("User");
90
-
91
- expect(schema.entity).toBe("User");
92
- expect(schema.table).toBe("users");
93
- expect(schema.fields).toEqual({ email: "user_email" });
94
- });
95
-
96
- it("should throw for unregistered entity", () => {
97
- expect(() => registry.getSchema("Unknown")).toThrow(
98
- "No schema registered for entity 'Unknown'"
99
- );
100
- });
101
- });
102
-
103
- describe("getTable", () => {
104
- it("should return table name", () => {
105
- registry.register({ entity: "User", table: "app_users" });
106
-
107
- expect(registry.getTable("User")).toBe("app_users");
108
- });
109
- });
110
-
111
- describe("mapFieldName", () => {
112
- beforeEach(() => {
113
- registry.register({
114
- entity: "User",
115
- table: "users",
116
- fields: {
117
- email: "user_email",
118
- createdAt: "created_at",
119
- },
120
- });
121
- });
122
-
123
- it("should map field with different name", () => {
124
- expect(registry.mapFieldName("User", "email")).toBe("user_email");
125
- expect(registry.mapFieldName("User", "createdAt")).toBe("created_at");
126
- });
127
-
128
- it("should return original name if not mapped", () => {
129
- expect(registry.mapFieldName("User", "name")).toBe("name");
130
- });
131
- });
132
-
133
- describe("mapFields", () => {
134
- beforeEach(() => {
135
- registry.register({
136
- entity: "User",
137
- table: "users",
138
- fields: {
139
- email: "user_email",
140
- name: "user_name",
141
- },
142
- });
143
- });
144
-
145
- it("should map all fields", () => {
146
- const data = {
147
- email: "test@test.com",
148
- name: "Test User",
149
- age: 25,
150
- };
151
-
152
- const mapped = registry.mapFields("User", data);
153
-
154
- expect(mapped).toEqual({
155
- user_email: "test@test.com",
156
- user_name: "Test User",
157
- age: 25,
158
- });
159
- });
160
-
161
- it("should ignore arrays", () => {
162
- const data = {
163
- email: "test@test.com",
164
- posts: [{ id: "1" }, { id: "2" }],
165
- };
166
-
167
- const mapped = registry.mapFields("User", data);
168
-
169
- expect(mapped).toEqual({ user_email: "test@test.com" });
170
- expect(mapped.posts).toBeUndefined();
171
- });
172
-
173
- it("should handle null and undefined values", () => {
174
- const data = {
175
- email: null,
176
- name: undefined,
177
- };
178
-
179
- const mapped = registry.mapFields("User", data);
180
-
181
- expect(mapped.user_email).toBeNull();
182
- expect(mapped.user_name).toBeUndefined();
183
- });
184
- });
185
-
186
- describe("mapEntity", () => {
187
- beforeEach(() => {
188
- registry.register({
189
- entity: "User",
190
- table: "users",
191
- fields: {
192
- email: "user_email",
193
- },
194
- });
195
- });
196
-
197
- it("should map entity with ID", () => {
198
- // Mock Entity
199
- const mockEntity = {
200
- id: { value: "user-123" },
201
- props: {
202
- email: "test@test.com",
203
- name: "Test",
204
- },
205
- };
206
-
207
- const mapped = registry.mapEntity("User", mockEntity as any);
208
-
209
- expect(mapped).toEqual({
210
- id: "user-123",
211
- user_email: "test@test.com",
212
- name: "Test",
213
- });
214
- });
215
-
216
- it("should skip nested entities and arrays", () => {
217
- const mockEntity = {
218
- id: { value: "user-123" },
219
- props: {
220
- email: "test@test.com",
221
- posts: [],
222
- address: { id: { value: "addr-1" } },
223
- },
224
- };
225
-
226
- const mapped = registry.mapEntity("User", mockEntity as any);
227
-
228
- expect(mapped.id).toBe("user-123");
229
- expect(mapped.user_email).toBe("test@test.com");
230
- expect(mapped.posts).toBeUndefined();
231
- expect(mapped.address).toBeUndefined();
232
- });
233
- });
234
-
235
- describe("getParentFk", () => {
236
- beforeEach(() => {
237
- registry.register({
238
- entity: "Post",
239
- table: "posts",
240
- parentFk: {
241
- field: "author_id",
242
- parentEntity: "User",
243
- },
244
- });
245
- registry.register({
246
- entity: "User",
247
- table: "users",
248
- });
249
- });
250
-
251
- it("should return FK object", () => {
252
- const fk = registry.getParentFk("Post", "user-123");
253
-
254
- expect(fk).toEqual({ author_id: "user-123" });
255
- });
256
-
257
- it("should return null if no parent FK defined", () => {
258
- const fk = registry.getParentFk("User", "user-123");
259
-
260
- expect(fk).toBeNull();
261
- });
262
- });
263
-
264
- describe("getParentEntity", () => {
265
- it("should return parent entity name", () => {
266
- registry.register({
267
- entity: "Post",
268
- table: "posts",
269
- parentFk: { field: "author_id", parentEntity: "User" },
270
- });
271
-
272
- expect(registry.getParentEntity("Post")).toBe("User");
273
- });
274
-
275
- it("should return null if no parent", () => {
276
- registry.register({ entity: "User", table: "users" });
277
-
278
- expect(registry.getParentEntity("User")).toBeNull();
279
- });
280
- });
281
-
282
- describe("getRegisteredEntities", () => {
283
- it("should return all registered entity names", () => {
284
- registry
285
- .register({ entity: "User", table: "users" })
286
- .register({ entity: "Post", table: "posts" })
287
- .register({ entity: "Comment", table: "comments" });
288
-
289
- expect(registry.getRegisteredEntities()).toEqual([
290
- "User",
291
- "Post",
292
- "Comment",
293
- ]);
294
- });
295
-
296
- it("should return empty array if none registered", () => {
297
- expect(registry.getRegisteredEntities()).toEqual([]);
298
- });
299
- });
300
-
301
- describe("clear", () => {
302
- it("should remove all schemas", () => {
303
- registry
304
- .register({ entity: "User", table: "users" })
305
- .register({ entity: "Post", table: "posts" });
306
-
307
- registry.clear();
308
-
309
- expect(registry.getRegisteredEntities()).toEqual([]);
310
- expect(registry.has("User")).toBe(false);
311
- });
312
- });
313
-
314
- describe("complex scenario", () => {
315
- beforeEach(() => {
316
- registry
317
- .register({
318
- entity: "User",
319
- table: "users",
320
- fields: {
321
- email: "user_email",
322
- name: "user_name",
323
- },
324
- })
325
- .register({
326
- entity: "Post",
327
- table: "blog_posts",
328
- fields: {
329
- content: "post_content",
330
- },
331
- parentFk: { field: "author_id", parentEntity: "User" },
332
- })
333
- .register({
334
- entity: "Comment",
335
- table: "post_comments",
336
- fields: {
337
- text: "comment_text",
338
- },
339
- parentFk: { field: "post_id", parentEntity: "Post" },
340
- })
341
- .register({
342
- entity: "Like",
343
- table: "comment_likes",
344
- fields: {
345
- userId: "user_id",
346
- },
347
- parentFk: { field: "comment_id", parentEntity: "Comment" },
348
- });
349
- });
350
-
351
- it("should handle full hierarchy", () => {
352
- expect(registry.getTable("User")).toBe("users");
353
- expect(registry.getTable("Post")).toBe("blog_posts");
354
- expect(registry.getTable("Comment")).toBe("post_comments");
355
- expect(registry.getTable("Like")).toBe("comment_likes");
356
-
357
- expect(registry.getParentEntity("Post")).toBe("User");
358
- expect(registry.getParentEntity("Comment")).toBe("Post");
359
- expect(registry.getParentEntity("Like")).toBe("Comment");
360
- expect(registry.getParentEntity("User")).toBeNull();
361
- });
362
-
363
- it("should map fields correctly for each entity", () => {
364
- expect(registry.mapFieldName("User", "email")).toBe("user_email");
365
- expect(registry.mapFieldName("Post", "content")).toBe("post_content");
366
- expect(registry.mapFieldName("Comment", "text")).toBe("comment_text");
367
- expect(registry.mapFieldName("Like", "userId")).toBe("user_id");
368
- });
369
-
370
- it("should build FK chain", () => {
371
- expect(registry.getParentFk("Post", "user-1")).toEqual({
372
- author_id: "user-1",
373
- });
374
- expect(registry.getParentFk("Comment", "post-1")).toEqual({
375
- post_id: "post-1",
376
- });
377
- expect(registry.getParentFk("Like", "comment-1")).toEqual({
378
- comment_id: "comment-1",
379
- });
380
- });
381
- });
382
- });
@@ -1,252 +0,0 @@
1
- import { z } from "zod";
2
- import {
3
- Id,
4
- Aggregate,
5
- EntityValidation,
6
- EntityHooks,
7
- ValidationError,
8
- } from "../src";
9
-
10
- const userSchema = z.object({
11
- id: z.custom<Id>((val) => val instanceof Id, { message: "Invalid Id" }),
12
- name: z.string().min(2, "Name must be at least 2 characters"),
13
- email: z.string().email("Invalid email format"),
14
- age: z.number().min(0, "Age cannot be negative").max(150, "Age is too high"),
15
- status: z.enum(["active", "inactive"]),
16
- });
17
-
18
- interface UserProps extends z.infer<typeof userSchema> {}
19
- class User extends Aggregate<UserProps> {
20
- protected static validation: EntityValidation<UserProps> = {
21
- schema: userSchema,
22
- config: {
23
- onCreate: true,
24
- onUpdate: true,
25
- throwOnError: true,
26
- },
27
- };
28
-
29
- protected static hooks: EntityHooks<UserProps, User> = {
30
- onCreate: (entity) => {},
31
- onBeforeUpdate: (entity, snapshot) => {
32
- if (snapshot.email !== entity.email) {
33
- return false;
34
- }
35
- return true;
36
- },
37
- rules: (entity) => {
38
- if (entity.name.toLowerCase() === "admin") {
39
- throw new Error("Name cannot be 'admin'");
40
- }
41
- },
42
- };
43
-
44
- get name(): string {
45
- return this.props.name;
46
- }
47
-
48
- set name(value: string) {
49
- this.props.name = value;
50
- }
51
-
52
- get email(): string {
53
- return this.props.email;
54
- }
55
-
56
- set email(value: string) {
57
- this.props.email = value;
58
- }
59
-
60
- get age(): number {
61
- return this.props.age;
62
- }
63
-
64
- set age(value: number) {
65
- this.props.age = value;
66
- }
67
-
68
- get status(): "active" | "inactive" {
69
- return this.props.status;
70
- }
71
-
72
- deactivate(): void {
73
- this.props.status = "inactive";
74
- }
75
-
76
- activate(): void {
77
- this.props.status = "active";
78
- }
79
- }
80
-
81
- class UserSafe extends Aggregate<UserProps> {
82
- protected static validation: EntityValidation<UserProps> = {
83
- schema: userSchema,
84
- config: {
85
- onCreate: true,
86
- onUpdate: true,
87
- throwOnError: false,
88
- },
89
- };
90
-
91
- protected static hooks: EntityHooks<UserProps, UserSafe> = {};
92
-
93
- get name(): string {
94
- return this.props.name;
95
- }
96
-
97
- get email(): string {
98
- return this.props.email;
99
- }
100
- }
101
-
102
- describe("Rich Domain with Standard Schema Validation", () => {
103
- describe("User Creation with Validation", () => {
104
- it("should create user with valid data", () => {
105
- const user = new User({
106
- name: "John Doe",
107
- email: "john@example.com",
108
- age: 25,
109
- status: "active",
110
- });
111
-
112
- expect(user).toBeInstanceOf(User);
113
- expect(user.name).toBe("John Doe");
114
- expect(user.email).toBe("john@example.com");
115
- expect(user.age).toBe(25);
116
- expect(user.isNew()).toBe(true);
117
- });
118
-
119
- it("should throw on invalid email", () => {
120
- expect(() => {
121
- new User({
122
- name: "John",
123
- email: "invalid-email",
124
- age: 30,
125
- status: "active",
126
- });
127
- }).toThrow(ValidationError);
128
- });
129
-
130
- it("should throw on invalid name (too short)", () => {
131
- expect(() => {
132
- new User({
133
- name: "J",
134
- email: "john@example.com",
135
- age: 30,
136
- status: "active",
137
- });
138
- }).toThrow(ValidationError);
139
- });
140
-
141
- it("should throw on custom rule violation", () => {
142
- expect(() => {
143
- new User({
144
- name: "admin",
145
- email: "admin@example.com",
146
- age: 30,
147
- status: "active",
148
- });
149
- }).toThrow(Error);
150
- });
151
-
152
- it("should not throw when throwOnError is false", () => {
153
- const user = new UserSafe({
154
- name: "J",
155
- email: "invalid",
156
- age: 30,
157
- status: "active",
158
- });
159
-
160
- expect(user).toBeInstanceOf(UserSafe);
161
- expect(user.hasValidationErrors).toBe(true);
162
- expect(user.validationErrors).toBeInstanceOf(ValidationError);
163
- expect(user.validationErrors?.issues.length).toBeGreaterThan(0);
164
- });
165
-
166
- it("should not have errors when valid and throwOnError is false", () => {
167
- const user = new UserSafe({
168
- name: "John Doe",
169
- email: "john@example.com",
170
- age: 30,
171
- status: "active",
172
- });
173
-
174
- expect(user).toBeInstanceOf(UserSafe);
175
- expect(user.hasValidationErrors).toBe(false);
176
- expect(user.validationErrors).toBeUndefined();
177
- expect(user.name).toBe("John Doe");
178
- });
179
- });
180
-
181
- describe("Update Validation", () => {
182
- it("should validate on property update", () => {
183
- const user = new User({
184
- name: "John Doe",
185
- email: "john@example.com",
186
- age: 30,
187
- status: "active",
188
- });
189
-
190
- expect(() => {
191
- user.name = "J";
192
- }).toThrow(ValidationError);
193
- });
194
-
195
- it("should allow valid updates", () => {
196
- const user = new User({
197
- name: "John Doe",
198
- email: "john@example.com",
199
- age: 30,
200
- status: "active",
201
- });
202
-
203
- user.name = "Jane Doe";
204
- expect(user.name).toBe("Jane Doe");
205
- });
206
-
207
- it("should block update via onBeforeUpdate hook", () => {
208
- const user = new User({
209
- name: "John Doe",
210
- email: "john@example.com",
211
- age: 30,
212
- status: "active",
213
- });
214
-
215
- user.email = "newemail@example.com";
216
-
217
- expect(user.email).toBe("john@example.com");
218
- });
219
-
220
- it("should validate custom rules on update", () => {
221
- const user = new User({
222
- name: "John Doe",
223
- email: "john@example.com",
224
- age: 30,
225
- status: "active",
226
- });
227
-
228
- expect(() => {
229
- user.name = "admin";
230
- }).toThrow(Error);
231
- });
232
- });
233
-
234
- describe("Serialization", () => {
235
- it("should serialize to JSON correctly", () => {
236
- const user = new User({
237
- name: "John Doe",
238
- email: "john@example.com",
239
- age: 30,
240
- status: "active",
241
- });
242
-
243
- const json = user.toJson();
244
-
245
- expect(json.name).toBe("John Doe");
246
- expect(json.email).toBe("john@example.com");
247
- expect(json.age).toBe(30);
248
- expect(json.status).toBe("active");
249
- expect(typeof json.id).toBe("string");
250
- });
251
- });
252
- });