@woltz/rich-domain 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/aggregate-changes.d.ts +164 -0
  3. package/dist/aggregate-changes.d.ts.map +1 -0
  4. package/dist/aggregate-changes.js +281 -0
  5. package/dist/aggregate-changes.js.map +1 -0
  6. package/dist/base-entity.d.ts +32 -8
  7. package/dist/base-entity.d.ts.map +1 -1
  8. package/dist/base-entity.js +117 -86
  9. package/dist/base-entity.js.map +1 -1
  10. package/dist/criteria.d.ts +3 -3
  11. package/dist/criteria.d.ts.map +1 -1
  12. package/dist/criteria.js.map +1 -1
  13. package/dist/crypto.d.ts +3 -0
  14. package/dist/crypto.d.ts.map +1 -0
  15. package/dist/crypto.js +29 -0
  16. package/dist/crypto.js.map +1 -0
  17. package/dist/entity-changes.d.ts +84 -0
  18. package/dist/entity-changes.d.ts.map +1 -0
  19. package/dist/entity-changes.js +135 -0
  20. package/dist/entity-changes.js.map +1 -0
  21. package/dist/entity-schema-registry.d.ts +148 -0
  22. package/dist/entity-schema-registry.d.ts.map +1 -0
  23. package/dist/entity-schema-registry.js +219 -0
  24. package/dist/entity-schema-registry.js.map +1 -0
  25. package/dist/history-tracker.d.ts +97 -0
  26. package/dist/history-tracker.d.ts.map +1 -0
  27. package/dist/history-tracker.js +805 -0
  28. package/dist/history-tracker.js.map +1 -0
  29. package/dist/id.d.ts +11 -10
  30. package/dist/id.d.ts.map +1 -1
  31. package/dist/id.js +4 -28
  32. package/dist/id.js.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/mapper.d.ts +1 -1
  38. package/dist/mapper.d.ts.map +1 -1
  39. package/dist/mapper.js.map +1 -1
  40. package/dist/repository/base-repository.d.ts +6 -32
  41. package/dist/repository/base-repository.d.ts.map +1 -1
  42. package/dist/repository/base-repository.js +0 -27
  43. package/dist/repository/base-repository.js.map +1 -1
  44. package/dist/repository/unit-of-work.d.ts +0 -25
  45. package/dist/repository/unit-of-work.d.ts.map +1 -1
  46. package/dist/repository/unit-of-work.js +0 -25
  47. package/dist/repository/unit-of-work.js.map +1 -1
  48. package/dist/types/change-tracker.d.ts +186 -0
  49. package/dist/types/change-tracker.d.ts.map +1 -0
  50. package/dist/types/change-tracker.js +2 -0
  51. package/dist/types/change-tracker.js.map +1 -0
  52. package/dist/types/criteria.d.ts +5 -1
  53. package/dist/types/criteria.d.ts.map +1 -1
  54. package/dist/types/history-tracker.d.ts +11 -0
  55. package/dist/types/history-tracker.d.ts.map +1 -1
  56. package/dist/types/utils.d.ts +0 -1
  57. package/dist/types/utils.d.ts.map +1 -1
  58. package/dist/validation-error.d.ts.map +1 -1
  59. package/dist/validation-error.js +0 -3
  60. package/dist/validation-error.js.map +1 -1
  61. package/dist/value-object.d.ts +57 -8
  62. package/dist/value-object.d.ts.map +1 -1
  63. package/dist/value-object.js +49 -21
  64. package/dist/value-object.js.map +1 -1
  65. package/package.json +2 -1
  66. package/src/aggregate-changes.ts +335 -0
  67. package/src/base-entity.ts +140 -100
  68. package/src/criteria.ts +2 -1
  69. package/src/crypto.ts +31 -0
  70. package/src/entity-changes.ts +151 -0
  71. package/src/entity-schema-registry.ts +275 -0
  72. package/src/history-tracker.ts +1114 -0
  73. package/src/id.ts +17 -26
  74. package/src/index.ts +1 -0
  75. package/src/mapper.ts +4 -1
  76. package/src/repository/base-repository.ts +6 -37
  77. package/src/repository/unit-of-work.ts +0 -25
  78. package/src/types/change-tracker.ts +221 -0
  79. package/src/types/criteria.ts +6 -1
  80. package/src/types/history-tracker.ts +13 -0
  81. package/src/types/utils.ts +0 -9
  82. package/src/validation-error.ts +0 -4
  83. package/src/value-object.ts +84 -23
  84. package/tests/aggregate-changes.test.ts +284 -0
  85. package/tests/criteria.test.ts +122 -161
  86. package/tests/entity-equality.test.ts +38 -61
  87. package/tests/entity-schema-registry.test.ts +382 -0
  88. package/tests/entity-validation.test.ts +7 -94
  89. package/tests/history-tracker.spec.ts +349 -617
  90. package/tests/id.test.ts +41 -44
  91. package/tests/load-test/data.json +346041 -0
  92. package/tests/load-test/entities.ts +97 -0
  93. package/tests/load-test/generate-data.ts +81 -0
  94. package/tests/load-test/lead-to-domain.mapper.ts +24 -0
  95. package/tests/load-test/load.test.ts +38 -0
  96. package/tests/repository.test.ts +30 -54
  97. package/tests/to-json.test.ts +14 -18
  98. package/tests/utils.ts +138 -102
  99. package/tests/value-objects.test.ts +57 -29
  100. package/dist/deep-proxy.d.ts +0 -36
  101. package/dist/deep-proxy.d.ts.map +0 -1
  102. package/dist/deep-proxy.js +0 -384
  103. package/dist/deep-proxy.js.map +0 -1
  104. package/src/deep-proxy.ts +0 -447
  105. package/tests/entity.test.ts +0 -33
@@ -0,0 +1,284 @@
1
+ // ============================================================================
2
+ // Tests: AggregateChanges
3
+ // ============================================================================
4
+
5
+ import { AggregateChanges } from "../src/aggregate-changes";
6
+
7
+ describe("AggregateChanges", () => {
8
+ let changes: AggregateChanges;
9
+
10
+ beforeEach(() => {
11
+ changes = new AggregateChanges();
12
+ });
13
+
14
+ describe("isEmpty / hasChanges", () => {
15
+ it("should be empty initially", () => {
16
+ expect(changes.isEmpty()).toBe(true);
17
+ expect(changes.hasChanges()).toBe(false);
18
+ expect(changes.count).toBe(0);
19
+ });
20
+
21
+ it("should not be empty after adding operation", () => {
22
+ changes.addCreate("User", { id: "1", name: "Test" }, 0);
23
+ expect(changes.isEmpty()).toBe(false);
24
+ expect(changes.hasChanges()).toBe(true);
25
+ expect(changes.count).toBe(1);
26
+ });
27
+ });
28
+
29
+ describe("addCreate", () => {
30
+ it("should add create operation", () => {
31
+ const data = { id: "1", name: "Test User" };
32
+ changes.addCreate("User", data, 0);
33
+
34
+ expect(changes.hasCreates()).toBe(true);
35
+ expect(changes.creates()).toHaveLength(1);
36
+ expect(changes.creates()[0]).toMatchObject({
37
+ type: "create",
38
+ entity: "User",
39
+ data,
40
+ depth: 0,
41
+ });
42
+ });
43
+
44
+ it("should add create with parent info", () => {
45
+ const data = { id: "post-1", title: "Test Post" };
46
+ changes.addCreate("Post", data, 1, "user-1", "User");
47
+
48
+ const create = changes.creates()[0];
49
+ expect(create.parentId).toBe("user-1");
50
+ expect(create.parentEntity).toBe("User");
51
+ });
52
+ });
53
+
54
+ describe("addUpdate", () => {
55
+ it("should add update operation", () => {
56
+ const data = { id: "1", name: "Updated" };
57
+ const changedFields = { name: "Updated" };
58
+ changes.addUpdate("User", "1", data, changedFields, 0);
59
+
60
+ expect(changes.hasUpdates()).toBe(true);
61
+ expect(changes.updates()).toHaveLength(1);
62
+ expect(changes.updates()[0]).toMatchObject({
63
+ type: "update",
64
+ entity: "User",
65
+ id: "1",
66
+ changedFields,
67
+ depth: 0,
68
+ });
69
+ });
70
+ });
71
+
72
+ describe("addDelete", () => {
73
+ it("should add delete operation", () => {
74
+ const data = { id: "1", name: "To Delete" };
75
+ changes.addDelete("User", "1", data, 0);
76
+
77
+ expect(changes.hasDeletes()).toBe(true);
78
+ expect(changes.deletes()).toHaveLength(1);
79
+ expect(changes.deletes()[0]).toMatchObject({
80
+ type: "delete",
81
+ entity: "User",
82
+ id: "1",
83
+ depth: 0,
84
+ });
85
+ });
86
+ });
87
+
88
+ describe("ordering", () => {
89
+ beforeEach(() => {
90
+ // Add operations in random order
91
+ changes.addCreate("Comment", { id: "c1" }, 2);
92
+ changes.addDelete("Like", "l1", { id: "l1" }, 3);
93
+ changes.addCreate("User", { id: "u1" }, 0);
94
+ changes.addDelete("Comment", "c2", { id: "c2" }, 2);
95
+ changes.addCreate("Post", { id: "p1" }, 1);
96
+ changes.addDelete("Post", "p2", { id: "p2" }, 1);
97
+ });
98
+
99
+ it("should order creates by depth ASC (root → leaf)", () => {
100
+ const creates = changes.creates();
101
+ expect(creates[0].entity).toBe("User"); // depth 0
102
+ expect(creates[1].entity).toBe("Post"); // depth 1
103
+ expect(creates[2].entity).toBe("Comment"); // depth 2
104
+ });
105
+
106
+ it("should order deletes by depth DESC (leaf → root)", () => {
107
+ const deletes = changes.deletes();
108
+ expect(deletes[0].entity).toBe("Like"); // depth 3
109
+ expect(deletes[1].entity).toBe("Comment"); // depth 2
110
+ expect(deletes[2].entity).toBe("Post"); // depth 1
111
+ });
112
+ });
113
+
114
+ describe("operations iterator", () => {
115
+ it("should yield operations in correct order: deletes, creates, updates", () => {
116
+ changes.addCreate("Post", { id: "p1" }, 1);
117
+ changes.addUpdate("User", "u1", { id: "u1" }, { name: "New" }, 0);
118
+ changes.addDelete("Comment", "c1", { id: "c1" }, 2);
119
+
120
+ const ops = [...changes.operations()];
121
+
122
+ expect(ops[0].type).toBe("delete"); // deletes first
123
+ expect(ops[1].type).toBe("create"); // creates second
124
+ expect(ops[2].type).toBe("update"); // updates last
125
+ });
126
+ });
127
+
128
+ describe("toBatchOperations", () => {
129
+ beforeEach(() => {
130
+ // Creates
131
+ changes.addCreate("User", { id: "u1" }, 0);
132
+ changes.addCreate("Post", { id: "p1" }, 1, "u1", "User");
133
+ changes.addCreate("Post", { id: "p2" }, 1, "u1", "User");
134
+ changes.addCreate("Comment", { id: "c1" }, 2, "p1", "Post");
135
+
136
+ // Updates
137
+ changes.addUpdate("User", "u2", { id: "u2" }, { name: "Updated" }, 0);
138
+ changes.addUpdate("Post", "p3", { id: "p3" }, { title: "New Title" }, 1);
139
+
140
+ // Deletes
141
+ changes.addDelete("Comment", "c2", { id: "c2" }, 2);
142
+ changes.addDelete("Comment", "c3", { id: "c3" }, 2);
143
+ changes.addDelete("Post", "p4", { id: "p4" }, 1);
144
+ });
145
+
146
+ it("should group deletes by entity and order by depth DESC", () => {
147
+ const batch = changes.toBatchOperations();
148
+
149
+ expect(batch.deletes).toHaveLength(2); // Comment and Post
150
+
151
+ // Comments should come first (depth 2)
152
+ expect(batch.deletes[0].entity).toBe("Comment");
153
+ expect(batch.deletes[0].ids).toEqual(["c2", "c3"]);
154
+
155
+ // Posts second (depth 1)
156
+ expect(batch.deletes[1].entity).toBe("Post");
157
+ expect(batch.deletes[1].ids).toEqual(["p4"]);
158
+ });
159
+
160
+ it("should group creates by entity and order by depth ASC", () => {
161
+ const batch = changes.toBatchOperations();
162
+
163
+ expect(batch.creates).toHaveLength(3); // User, Post, Comment
164
+
165
+ // User first (depth 0)
166
+ expect(batch.creates[0].entity).toBe("User");
167
+ expect(batch.creates[0].items).toHaveLength(1);
168
+
169
+ // Post second (depth 1)
170
+ expect(batch.creates[1].entity).toBe("Post");
171
+ expect(batch.creates[1].items).toHaveLength(2);
172
+
173
+ // Comment last (depth 2)
174
+ expect(batch.creates[2].entity).toBe("Comment");
175
+ expect(batch.creates[2].items).toHaveLength(1);
176
+ });
177
+
178
+ it("should group updates by entity", () => {
179
+ const batch = changes.toBatchOperations();
180
+
181
+ expect(batch.updates).toHaveLength(2); // User and Post
182
+
183
+ const userUpdates = batch.updates.find((u) => u.entity === "User");
184
+ expect(userUpdates?.items).toHaveLength(1);
185
+ expect(userUpdates?.items[0].id).toBe("u2");
186
+
187
+ const postUpdates = batch.updates.find((u) => u.entity === "Post");
188
+ expect(postUpdates?.items).toHaveLength(1);
189
+ expect(postUpdates?.items[0].id).toBe("p3");
190
+ });
191
+
192
+ it("should include parentId in create items", () => {
193
+ const batch = changes.toBatchOperations();
194
+
195
+ const postCreates = batch.creates.find((c) => c.entity === "Post");
196
+ expect(postCreates?.items[0].parentId).toBe("u1");
197
+ expect(postCreates?.items[1].parentId).toBe("u1");
198
+
199
+ const commentCreates = batch.creates.find((c) => c.entity === "Comment");
200
+ expect(commentCreates?.items[0].parentId).toBe("p1");
201
+ });
202
+ });
203
+
204
+ describe("for (filter by entity)", () => {
205
+ beforeEach(() => {
206
+ changes.addCreate("Post", { id: "p1", title: "Post 1" }, 1);
207
+ changes.addCreate("Post", { id: "p2", title: "Post 2" }, 1);
208
+ changes.addUpdate("Post", "p3", { id: "p3" }, { title: "Updated" }, 1);
209
+ changes.addDelete("Post", "p4", { id: "p4" }, 1);
210
+ changes.addCreate("Comment", { id: "c1" }, 2);
211
+ });
212
+
213
+ it("should filter creates by entity", () => {
214
+ const postChanges = changes.for("Post");
215
+ expect(postChanges.creates).toHaveLength(2);
216
+ expect(postChanges.creates[0].id).toBe("p1");
217
+ });
218
+
219
+ it("should filter updates by entity", () => {
220
+ const postChanges = changes.for("Post");
221
+ expect(postChanges.updates).toHaveLength(1);
222
+ expect(postChanges.updates[0].entity.id).toBe("p3");
223
+ });
224
+
225
+ it("should filter deletes by entity", () => {
226
+ const postChanges = changes.for("Post");
227
+ expect(postChanges.deletes).toHaveLength(1);
228
+ expect(postChanges.deletes[0].id).toBe("p4");
229
+ });
230
+
231
+ it("should return empty for non-existent entity", () => {
232
+ const userChanges = changes.for("User");
233
+ expect(userChanges.isEmpty()).toBe(true);
234
+ });
235
+
236
+ it("should have helper methods", () => {
237
+ const postChanges = changes.for("Post");
238
+ expect(postChanges.hasCreates()).toBe(true);
239
+ expect(postChanges.hasUpdates()).toBe(true);
240
+ expect(postChanges.hasDeletes()).toBe(true);
241
+ expect(postChanges.hasChanges()).toBe(true);
242
+ });
243
+ });
244
+
245
+ describe("getAffectedEntities", () => {
246
+ it("should return list of unique entities", () => {
247
+ changes.addCreate("User", { id: "u1" }, 0);
248
+ changes.addCreate("Post", { id: "p1" }, 1);
249
+ changes.addUpdate("Post", "p2", { id: "p2" }, {}, 1);
250
+ changes.addDelete("Comment", "c1", { id: "c1" }, 2);
251
+
252
+ const entities = changes.getAffectedEntities();
253
+
254
+ expect(entities).toContain("User");
255
+ expect(entities).toContain("Post");
256
+ expect(entities).toContain("Comment");
257
+ expect(entities).toHaveLength(3);
258
+ });
259
+ });
260
+
261
+ describe("clone", () => {
262
+ it("should create independent copy", () => {
263
+ changes.addCreate("User", { id: "u1" }, 0);
264
+
265
+ const cloned = changes.clone();
266
+ cloned.addCreate("Post", { id: "p1" }, 1);
267
+
268
+ expect(changes.count).toBe(1);
269
+ expect(cloned.count).toBe(2);
270
+ });
271
+ });
272
+
273
+ describe("clear", () => {
274
+ it("should remove all operations", () => {
275
+ changes.addCreate("User", { id: "u1" }, 0);
276
+ changes.addUpdate("Post", "p1", { id: "p1" }, {}, 1);
277
+
278
+ changes.clear();
279
+
280
+ expect(changes.isEmpty()).toBe(true);
281
+ expect(changes.count).toBe(0);
282
+ });
283
+ });
284
+ });
@@ -24,6 +24,17 @@ interface UserWithPostsDto {
24
24
  }[];
25
25
  }
26
26
 
27
+ interface UserWithNestedObject {
28
+ id: string;
29
+ name: string;
30
+ address: {
31
+ street: string;
32
+ city: string;
33
+ country: string;
34
+ };
35
+ tags: string[];
36
+ }
37
+
27
38
  const testUsers: TestUser[] = [
28
39
  {
29
40
  id: "1",
@@ -68,26 +79,12 @@ const testUsers: TestUser[] = [
68
79
  ];
69
80
 
70
81
  describe("Criteria", () => {
71
- describe("Search", () => {
72
- it("should search by all items ignoring pagination and limit", () => {
73
- const criteria = Criteria.create<TestUser>()
74
- .search(["name"], "Eve")
75
- .paginate(3, 1);
76
- const result = PaginatedResult.fromArray(testUsers, criteria);
77
- expect(result.data).toHaveLength(1);
78
- expect(result.data[0].name).toBe("Eve");
79
- expect(result.meta.total).toBe(1);
80
- expect(result.meta.totalPages).toBe(1);
81
- expect(result.meta.page).toBe(1);
82
- expect(result.meta.limit).toBe(1);
83
- });
84
- });
85
82
  describe("Fluent API", () => {
86
83
  it("should create empty criteria", () => {
87
84
  const criteria = Criteria.create<TestUser>();
88
85
  expect(criteria.hasFilters()).toBe(false);
89
86
  expect(criteria.hasOrders()).toBe(false);
90
- expect(criteria.hasPagination()).toBe(true); // Default pagination is set
87
+ expect(criteria.hasPagination()).toBe(true);
91
88
  });
92
89
 
93
90
  it("should chain methods fluently", () => {
@@ -118,27 +115,6 @@ describe("Criteria", () => {
118
115
  });
119
116
 
120
117
  describe("Filtering", () => {
121
- it("should filter by equals", () => {
122
- const criteria = Criteria.create<TestUser>().whereEquals(
123
- "status",
124
- "active"
125
- );
126
- const result = PaginatedResult.fromArray(testUsers, criteria);
127
- expect(result.data).toHaveLength(3);
128
- expect(result.data.every((u) => u.status === "active")).toBe(true);
129
- });
130
-
131
- it("should filter by notEquals", () => {
132
- const criteria = Criteria.create<TestUser>().where(
133
- "status",
134
- "notEquals",
135
- "active"
136
- );
137
- const result = PaginatedResult.fromArray(testUsers, criteria);
138
- expect(result.data).toHaveLength(2);
139
- expect(result.data.every((u) => u.status === "inactive")).toBe(true);
140
- });
141
-
142
118
  it("should filter by search", () => {
143
119
  const criteria = Criteria.create<TestUser>().search(["name"], "Bob");
144
120
 
@@ -147,6 +123,19 @@ describe("Criteria", () => {
147
123
  expect(result.data[0].name).toBe("Bob");
148
124
  });
149
125
 
126
+ it("should search by all items ignoring pagination and limit", () => {
127
+ const criteria = Criteria.create<TestUser>()
128
+ .search(["name"], "Eve")
129
+ .paginate(3, 1);
130
+ const result = PaginatedResult.fromArray(testUsers, criteria);
131
+ expect(result.data).toHaveLength(1);
132
+ expect(result.data[0].name).toBe("Eve");
133
+ expect(result.meta.total).toBe(1);
134
+ expect(result.meta.totalPages).toBe(1);
135
+ expect(result.meta.page).toBe(1);
136
+ expect(result.meta.limit).toBe(1);
137
+ });
138
+
150
139
  it("should filter by greaterThan", () => {
151
140
  const criteria = Criteria.create<TestUser>().where(
152
141
  "age",
@@ -228,6 +217,20 @@ describe("Criteria", () => {
228
217
  expect(result.data).toHaveLength(2);
229
218
  expect(result.data.map((u) => u.name)).toEqual(["Bob", "Diana"]);
230
219
  });
220
+
221
+ it("should filter, order, and paginate", () => {
222
+ const criteria = Criteria.create<TestUser>()
223
+ .whereEquals("status", "active")
224
+ .orderByDesc("age")
225
+ .paginate(1, 2);
226
+
227
+ const result = PaginatedResult.fromArray(testUsers, criteria);
228
+
229
+ expect(result.data).toHaveLength(2);
230
+ expect(result.data.map((u) => u.name)).toEqual(["Bob", "Diana"]);
231
+ expect(result.meta.total).toBe(3);
232
+ expect(result.meta.totalPages).toBe(2);
233
+ });
231
234
  });
232
235
 
233
236
  describe("Ordering", () => {
@@ -274,35 +277,6 @@ describe("Criteria", () => {
274
277
  expect(result.meta.hasPrevious).toBe(false);
275
278
  });
276
279
 
277
- it("should get second page", () => {
278
- const criteria = Criteria.create<TestUser>().paginate(2, 2);
279
- const result = PaginatedResult.fromArray(testUsers, criteria);
280
-
281
- expect(result.data).toHaveLength(2);
282
- expect(result.data.map((u) => u.name)).toEqual(["Charlie", "Diana"]);
283
- expect(result.meta.page).toBe(2);
284
- expect(result.meta.hasNext).toBe(true);
285
- expect(result.meta.hasPrevious).toBe(true);
286
- });
287
-
288
- it("should get last page", () => {
289
- const criteria = Criteria.create<TestUser>().paginate(3, 2);
290
- const result = PaginatedResult.fromArray(testUsers, criteria);
291
-
292
- expect(result.data).toHaveLength(1);
293
- expect(result.data[0].name).toBe("Eve");
294
- expect(result.meta.hasNext).toBe(false);
295
- expect(result.meta.hasPrevious).toBe(true);
296
- });
297
-
298
- it("should handle empty page", () => {
299
- const criteria = Criteria.create<TestUser>().paginate(10, 2);
300
- const result = PaginatedResult.fromArray(testUsers, criteria);
301
-
302
- expect(result.data).toHaveLength(0);
303
- expect(result.meta.total).toBe(5);
304
- });
305
-
306
280
  it("should apply limit shorthand", () => {
307
281
  const criteria = Criteria.create<TestUser>().limit(3);
308
282
  const result = PaginatedResult.fromArray(testUsers, criteria);
@@ -310,21 +284,17 @@ describe("Criteria", () => {
310
284
  expect(result.data).toHaveLength(3);
311
285
  expect(result.meta.page).toBe(1);
312
286
  });
313
- });
314
-
315
- describe("Combined Operations", () => {
316
- it("should filter, order, and paginate", () => {
317
- const criteria = Criteria.create<TestUser>()
318
- .whereEquals("status", "active")
319
- .orderByDesc("age")
320
- .paginate(1, 2);
321
287
 
322
- const result = PaginatedResult.fromArray(testUsers, criteria);
288
+ it("should create pagination meta", () => {
289
+ const pagination = { page: 2, limit: 10, offset: 10 };
290
+ const meta = PaginatedResult.createMeta(pagination, 45);
323
291
 
324
- expect(result.data).toHaveLength(2);
325
- expect(result.data.map((u) => u.name)).toEqual(["Bob", "Diana"]);
326
- expect(result.meta.total).toBe(3);
327
- expect(result.meta.totalPages).toBe(2);
292
+ expect(meta.page).toBe(2);
293
+ expect(meta.limit).toBe(10);
294
+ expect(meta.total).toBe(45);
295
+ expect(meta.totalPages).toBe(5);
296
+ expect(meta.hasNext).toBe(true);
297
+ expect(meta.hasPrevious).toBe(true);
328
298
  });
329
299
  });
330
300
 
@@ -374,12 +344,14 @@ describe("Criteria", () => {
374
344
  new Post({
375
345
  title: "Post 1",
376
346
  content: "Content 1",
377
- likes: 1,
347
+ published: true,
348
+ comments: [],
378
349
  }),
379
350
  new Post({
380
351
  title: "Post 2",
381
352
  content: "Content 2",
382
- likes: 2,
353
+ published: true,
354
+ comments: [],
383
355
  }),
384
356
  ];
385
357
 
@@ -421,30 +393,6 @@ describe("Criteria", () => {
421
393
  });
422
394
  });
423
395
 
424
- describe("Helper Functions", () => {
425
- it("should create pagination meta", () => {
426
- const pagination = { page: 2, limit: 10, offset: 10 };
427
- const meta = PaginatedResult.createMeta(pagination, 45);
428
-
429
- expect(meta.page).toBe(2);
430
- expect(meta.limit).toBe(10);
431
- expect(meta.total).toBe(45);
432
- expect(meta.totalPages).toBe(5);
433
- expect(meta.hasNext).toBe(true);
434
- expect(meta.hasPrevious).toBe(true);
435
- });
436
-
437
- it("should create paginated result", () => {
438
- const data = [{ id: "1" }, { id: "2" }];
439
- const pagination = { page: 1, limit: 2, offset: 0 };
440
- const result = PaginatedResult.create(data, pagination, data.length);
441
-
442
- expect(result.data).toEqual(data);
443
- expect(result.meta.total).toBe(data.length);
444
- expect(result.meta.totalPages).toBe(1);
445
- });
446
- });
447
-
448
396
  describe("Criteria from Query Params", () => {
449
397
  it("should create criteria from query params", () => {
450
398
  const queryParams = {
@@ -466,6 +414,19 @@ describe("Criteria", () => {
466
414
 
467
415
  describe("Quantifiers", () => {
468
416
  describe("Fluent API methods", () => {
417
+ it("should use quantifiers horthand methods", () => {
418
+ // CHECK: Devemos permitir o encadeamento de metodos quantifiers?
419
+ const criteria = Criteria.create<TestUser>()
420
+ .whereSome("name", "contains", "ali")
421
+ .whereEvery("name", "contains", "ali")
422
+ .whereNone("name", "contains", "ali");
423
+
424
+ const filters = criteria.getFilters().map((f) => f.options?.quantifier);
425
+
426
+ expect(filters).toHaveLength(3);
427
+ expect(filters).toEqual(["some", "every", "none"]);
428
+ });
429
+
469
430
  it("should create filter with whereSome", () => {
470
431
  const criteria = Criteria.create<UserWithPostsDto>().whereSome(
471
432
  "posts.title",
@@ -506,20 +467,6 @@ describe("Criteria", () => {
506
467
  expect(filters[0].options?.quantifier).toBe("none");
507
468
  });
508
469
 
509
- it("should create filter without quantifier using where", () => {
510
- const criteria = Criteria.create<UserWithPostsDto>().where(
511
- "posts.title",
512
- "contains",
513
- "test"
514
- );
515
-
516
- const filters = criteria.getFilters();
517
- expect(filters).toHaveLength(1);
518
- expect(filters[0].options).toBeUndefined();
519
- });
520
- });
521
-
522
- describe("fromQueryParams with quantifier", () => {
523
470
  it("should parse quantifier from query params with @some", () => {
524
471
  const queryParams = {
525
472
  "posts.title:contains@some": "test",
@@ -536,32 +483,6 @@ describe("Criteria", () => {
536
483
  expect(filters[0].options?.quantifier).toBe("some");
537
484
  });
538
485
 
539
- it("should parse quantifier from query params with @every", () => {
540
- const queryParams = {
541
- "posts.title:equals@every": "test",
542
- };
543
-
544
- const criteria =
545
- Criteria.fromQueryParams<UserWithPostsDto>(queryParams);
546
- const filters = criteria.getFilters();
547
-
548
- expect(filters).toHaveLength(1);
549
- expect(filters[0].options?.quantifier).toBe("every");
550
- });
551
-
552
- it("should parse quantifier from query params with @none", () => {
553
- const queryParams = {
554
- "posts.title:contains@none": "spam",
555
- };
556
-
557
- const criteria =
558
- Criteria.fromQueryParams<UserWithPostsDto>(queryParams);
559
- const filters = criteria.getFilters();
560
-
561
- expect(filters).toHaveLength(1);
562
- expect(filters[0].options?.quantifier).toBe("none");
563
- });
564
-
565
486
  it("should work with in operator and quantifier", () => {
566
487
  const queryParams = {
567
488
  "posts.title:in@some": "test1,test2,test3",
@@ -592,22 +513,6 @@ describe("Criteria", () => {
592
513
  expect(filters[0].options?.quantifier).toBe("some");
593
514
  });
594
515
 
595
- it("should maintain backward compatibility without quantifier", () => {
596
- const queryParams = {
597
- "posts.title:contains": "test",
598
- };
599
-
600
- const criteria =
601
- Criteria.fromQueryParams<UserWithPostsDto>(queryParams);
602
- const filters = criteria.getFilters();
603
-
604
- expect(filters).toHaveLength(1);
605
- expect(filters[0].field).toBe("posts.title");
606
- expect(filters[0].operator).toBe("contains");
607
- expect(filters[0].value).toBe("test");
608
- expect(filters[0].options).toBeUndefined();
609
- });
610
-
611
516
  it("should throw error for invalid quantifier", () => {
612
517
  const queryParams = {
613
518
  "posts.title:contains@invalid": "test",
@@ -741,7 +646,7 @@ describe("Criteria", () => {
741
646
  {
742
647
  field: "posts",
743
648
  operator: "in",
744
- value: [{ title: "test", content: "test" }],
649
+ value: [{ title: "test" }] as any,
745
650
  },
746
651
  ],
747
652
  },
@@ -751,5 +656,61 @@ describe("Criteria", () => {
751
656
  expect(filters).toHaveLength(1);
752
657
  expect(filters[0].field).toBe("user_posts");
753
658
  });
659
+
660
+ it("should only allow nested paths in fromObject for object fields", () => {
661
+ // Only nested paths should work for object fields
662
+ const criteria = Criteria.fromObject<UserWithNestedObject>({
663
+ filters: [
664
+ {
665
+ field: "address.street",
666
+ operator: "equals",
667
+ value: "123 Main St",
668
+ },
669
+ {
670
+ field: "address.city",
671
+ operator: "equals",
672
+ value: "New York",
673
+ },
674
+ {
675
+ field: "id",
676
+ operator: "equals",
677
+ value: "user-1",
678
+ },
679
+ ],
680
+ });
681
+
682
+ expect(criteria.getFilters()).toHaveLength(3);
683
+ });
684
+ });
685
+
686
+ describe("Nested Object Field Paths", () => {
687
+ it("should only allow nested paths for object fields, not the root", () => {
688
+ const criteria = Criteria.create<UserWithNestedObject>();
689
+
690
+ // These should work - nested paths
691
+ criteria.whereEquals("address.city", "123 Main St");
692
+ criteria.whereEquals("address.city", "New York");
693
+ criteria.whereEquals("address.country", "USA");
694
+
695
+ // These should work - primitives and arrays
696
+ criteria.whereEquals("id", "user-1");
697
+ criteria.whereEquals("name", "John Doe");
698
+ criteria.where("tags", "in", ["tag1", "tag2"]);
699
+
700
+ const filters = criteria.getFilters();
701
+ expect(filters).toHaveLength(6);
702
+ });
703
+
704
+ it("should work with array of objects (keeps root + nested)", () => {
705
+ const criteria = Criteria.create<UserWithPostsDto>();
706
+
707
+ // These should work - array root and nested paths
708
+ criteria.where("posts", "in", []);
709
+ criteria.whereEquals("posts.title", "Test Post");
710
+ criteria.whereEquals("posts.content", "Test Content");
711
+
712
+ const filters = criteria.getFilters();
713
+ expect(filters).toHaveLength(3);
714
+ });
754
715
  });
755
716
  });