@woltz/rich-domain 1.2.4 → 1.3.0
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/dist/aggregate-changes.d.ts +56 -14
- package/dist/aggregate-changes.d.ts.map +1 -1
- package/dist/aggregate-changes.js +102 -22
- package/dist/aggregate-changes.js.map +1 -1
- package/dist/base-entity.d.ts +1 -1
- package/dist/base-entity.d.ts.map +1 -1
- package/dist/base-entity.js +11 -8
- package/dist/base-entity.js.map +1 -1
- package/dist/change-tracker.d.ts +1 -0
- package/dist/change-tracker.d.ts.map +1 -1
- package/dist/change-tracker.js +41 -27
- package/dist/change-tracker.js.map +1 -1
- package/dist/criteria.d.ts +7 -15
- package/dist/criteria.d.ts.map +1 -1
- package/dist/criteria.js +99 -76
- package/dist/criteria.js.map +1 -1
- package/dist/entity-schema-registry.d.ts +133 -3
- package/dist/entity-schema-registry.d.ts.map +1 -1
- package/dist/entity-schema-registry.js +155 -4
- package/dist/entity-schema-registry.js.map +1 -1
- package/dist/paginated-result.d.ts +4 -4
- package/dist/paginated-result.d.ts.map +1 -1
- package/dist/paginated-result.js +6 -20
- package/dist/paginated-result.js.map +1 -1
- package/dist/types/change-tracker.d.ts +30 -0
- package/dist/types/change-tracker.d.ts.map +1 -1
- package/dist/types/criteria.d.ts +1 -4
- package/dist/types/criteria.d.ts.map +1 -1
- package/dist/types/domain.d.ts +2 -0
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/types/utils.d.ts +2 -2
- package/dist/utils/helpers.d.ts +1 -0
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +23 -0
- package/dist/utils/helpers.js.map +1 -1
- package/dist/value-object.d.ts +1 -1
- package/dist/value-object.js +1 -1
- package/package.json +17 -3
- package/src/aggregate-changes.ts +133 -24
- package/src/base-entity.ts +13 -8
- package/src/change-tracker.ts +107 -38
- package/src/criteria.ts +151 -109
- package/src/entity-schema-registry.ts +253 -6
- package/src/paginated-result.ts +10 -30
- package/src/types/change-tracker.ts +31 -0
- package/src/types/criteria.ts +1 -4
- package/src/types/domain.ts +2 -0
- package/src/types/utils.ts +2 -2
- package/src/utils/helpers.ts +28 -0
- package/src/value-object.ts +1 -1
- package/.versionrc.json +0 -21
- package/CHANGELOG.md +0 -163
- package/tests/aggregate-changes.test.ts +0 -284
- package/tests/criteria.test.ts +0 -716
- package/tests/depth/deep-tracking.test.ts +0 -554
- package/tests/domain-events.test.ts +0 -431
- package/tests/entity-equality.test.ts +0 -464
- package/tests/entity-schema-registry.test.ts +0 -382
- package/tests/entity-validation.test.ts +0 -252
- package/tests/history-tracker.spec.ts +0 -439
- package/tests/id.test.ts +0 -338
- package/tests/load-test/data.json +0 -347211
- package/tests/load-test/entities.ts +0 -97
- package/tests/load-test/generate-data.ts +0 -81
- package/tests/load-test/lead-to-domain.mapper.ts +0 -24
- package/tests/load-test/load.test.ts +0 -38
- package/tests/repository.test.ts +0 -635
- package/tests/to-json.test.ts +0 -99
- package/tests/utils.ts +0 -290
- package/tests/value-object-validation.test.ts +0 -219
- package/tests/value-objects.test.ts +0 -80
- 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
|
-
});
|