@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
package/tests/utils.ts CHANGED
@@ -1,7 +1,3 @@
1
- // ============================================================================
2
- // Test Entities & Value Objects
3
- // ============================================================================
4
-
5
1
  import {
6
2
  Aggregate,
7
3
  Criteria,
@@ -14,151 +10,206 @@ import {
14
10
  ValueObject,
15
11
  } from "../src";
16
12
 
17
- interface AddressProps {
18
- street: string;
19
- city: string;
20
- zipCode: string;
13
+ export class Like extends ValueObject<{
14
+ postId: string;
15
+ userId: string;
16
+ createdAt: Date;
17
+ }> {
18
+ static readonly identityKey = ["postId", "userId"];
19
+
20
+ get postId() {
21
+ return this.props.postId;
22
+ }
23
+ get userId() {
24
+ return this.props.userId;
25
+ }
21
26
  }
22
27
 
23
- class Address extends ValueObject<AddressProps> {
24
- get street(): string {
28
+ export class Address extends Entity<{
29
+ id: Id;
30
+ street: string;
31
+ city: string;
32
+ }> {
33
+ get street() {
25
34
  return this.props.street;
26
35
  }
27
-
28
- get city(): string {
36
+ get city() {
29
37
  return this.props.city;
30
38
  }
31
39
 
32
- get zipCode(): string {
33
- return this.props.zipCode;
40
+ changeStreet(street: string) {
41
+ this.props.street = street;
34
42
  }
35
43
  }
36
44
 
37
- interface PostProps {
38
- id: Id;
39
- title: string;
40
- content: string;
41
- likes: number;
42
- }
45
+ export class TagReference extends ValueObject<{ tagId: string; name: string }> {
46
+ static readonly identityKey = "tagId";
43
47
 
44
- class Post extends Entity<PostProps> {
45
- get title(): string {
46
- return this.props.title;
48
+ get tagId() {
49
+ return this.props.tagId;
47
50
  }
48
-
49
- set title(value: string) {
50
- this.props.title = value;
51
+ get name() {
52
+ return this.props.name;
51
53
  }
54
+ }
52
55
 
53
- get content(): string {
54
- return this.props.content;
56
+ export class Comment extends Entity<{
57
+ id: Id;
58
+ text: string;
59
+ authorId: string;
60
+ likes: Like[];
61
+ }> {
62
+ get text() {
63
+ return this.props.text;
64
+ }
65
+ get authorId() {
66
+ return this.props.authorId;
67
+ }
68
+ get likes() {
69
+ return this.props.likes;
55
70
  }
56
71
 
57
- set content(value: string) {
58
- this.props.content = value;
72
+ changeText(text: string) {
73
+ this.props.text = text;
59
74
  }
60
75
 
61
- get likes(): number {
62
- return this.props.likes;
76
+ addLike(like: Like) {
77
+ this.props.likes.push(like);
63
78
  }
64
79
 
65
- set likes(value: number) {
66
- this.props.likes = value;
80
+ removeLike(postId: string, userId: string) {
81
+ this.props.likes = this.props.likes.filter(
82
+ (l) => !(l.postId === postId && l.userId === userId)
83
+ );
67
84
  }
68
85
  }
69
-
70
- interface CommentProps {
86
+ export class Post extends Entity<{
71
87
  id: Id;
72
- text: string;
73
- author: string;
74
- }
88
+ title: string;
89
+ content: string;
90
+ published: boolean;
91
+ comments: Comment[];
92
+ }> {
93
+ get title() {
94
+ return this.props.title;
95
+ }
96
+ get content() {
97
+ return this.props.content;
98
+ }
99
+ get published() {
100
+ return this.props.published;
101
+ }
102
+ get comments() {
103
+ return this.props.comments;
104
+ }
75
105
 
76
- class Comment extends Entity<CommentProps> {
77
- get text(): string {
78
- return this.props.text;
106
+ set comments(comments: Comment[]) {
107
+ this.props.comments = comments;
108
+ }
109
+
110
+ changeTitle(title: string) {
111
+ this.props.title = title;
79
112
  }
80
113
 
81
- set text(value: string) {
82
- this.props.text = value;
114
+ publish() {
115
+ this.props.published = true;
83
116
  }
84
117
 
85
- get author(): string {
86
- return this.props.author;
118
+ addComment(comment: Comment) {
119
+ this.props.comments.push(comment);
120
+ }
121
+
122
+ removeComment(commentId: Id) {
123
+ this.props.comments = this.props.comments.filter(
124
+ (c) => c.id.value !== commentId.value
125
+ );
87
126
  }
88
127
  }
89
128
 
90
- interface UserProps {
129
+ export class User extends Entity<{
91
130
  id: Id;
92
131
  name: string;
93
132
  email: string;
133
+ address: Address | null;
94
134
  posts: Post[];
95
- address: Address;
96
- comments: Comment[];
97
- extra?: {
98
- age: number;
99
- height: number;
100
- };
101
- }
102
-
103
- class User extends Aggregate<UserProps> {
104
- get name(): string {
135
+ tags: TagReference[];
136
+ }> {
137
+ get name() {
105
138
  return this.props.name;
106
139
  }
107
-
108
- set name(value: string) {
109
- this.props.name = value;
110
- }
111
-
112
- get email(): string {
140
+ get email() {
113
141
  return this.props.email;
114
142
  }
115
-
116
- get posts(): Post[] {
143
+ get address() {
144
+ return this.props.address;
145
+ }
146
+ get posts() {
117
147
  return this.props.posts;
118
148
  }
149
+ get tags() {
150
+ return this.props.tags;
151
+ }
119
152
 
120
- set posts(value: Post[]) {
121
- this.props.posts = value;
153
+ set posts(posts: Post[]) {
154
+ this.props.posts = posts;
122
155
  }
123
156
 
124
- get address(): Address {
125
- return this.props.address;
157
+ changeName(name: string) {
158
+ this.props.name = name;
126
159
  }
127
160
 
128
- set address(value: Address) {
129
- this.props.address = value;
161
+ changeEmail(email: string) {
162
+ this.props.email = email;
130
163
  }
131
164
 
132
- get comments(): Comment[] {
133
- return this.props.comments;
165
+ setAddress(address: Address) {
166
+ this.props.address = address;
134
167
  }
135
168
 
136
- set comments(value: Comment[]) {
137
- this.props.comments = value;
169
+ removeAddress() {
170
+ this.props.address = null;
138
171
  }
139
172
 
140
- public addPost(post: Post) {
173
+ addPost(post: Post) {
141
174
  this.props.posts.push(post);
142
175
  }
143
176
 
144
- public addManyPosts(posts: Post[]) {
145
- this.props.posts.push(...posts);
177
+ removePost(postId: Id) {
178
+ this.props.posts = this.props.posts.filter(
179
+ (p) => p.id.value !== postId.value
180
+ );
146
181
  }
147
182
 
148
- public removePostById(id: string) {
149
- this.props.posts = this.props.posts.filter((post) => post.id.value !== id);
183
+ addTag(tag: TagReference) {
184
+ this.props.tags.push(tag);
150
185
  }
151
186
 
152
- public changeEmail(email: string) {
153
- this.props.email = email;
187
+ addManyTags(tags: TagReference[]) {
188
+ this.props.tags.push(...tags);
154
189
  }
155
190
 
156
- public changeExtra(extra: { age: number; height: number }) {
157
- this.props.extra = extra;
191
+ addManyPosts(posts: Post[]) {
192
+ this.props.posts.push(...posts);
193
+ }
194
+
195
+ removeTag(tagId: string) {
196
+ this.props.tags = this.props.tags.filter((t) => t.tagId !== tagId);
197
+ }
198
+
199
+ public getTypedChanges() {
200
+ type UserEntities = {
201
+ User: User;
202
+ Post: Post;
203
+ Comment: Comment;
204
+ Address: Address;
205
+ TagReference: TagReference;
206
+ Like: Like;
207
+ };
208
+ return this.getChanges<UserEntities>();
158
209
  }
159
210
  }
160
211
 
161
- class InMemoryRepository<
212
+ export class InMemoryRepository<
162
213
  TDomain extends Aggregate<any>
163
214
  > extends Repository<TDomain> {
164
215
  protected items: Map<string, TDomain> = new Map();
@@ -199,17 +250,13 @@ class InMemoryRepository<
199
250
  return result.data.length > 0 ? result.data[0] : null;
200
251
  }
201
252
 
202
- async create(aggregate: TDomain): Promise<void> {
253
+ async save(aggregate: TDomain): Promise<void> {
203
254
  this.items.set(aggregate.id.value, aggregate);
204
255
  }
205
256
 
206
- async update(entity: TDomain): Promise<void> {
207
- this.items.set(entity.id.value, entity);
208
- }
209
-
210
257
  async createMany(aggregates: TDomain[]): Promise<void> {
211
258
  for (const aggregate of aggregates) {
212
- await this.create(aggregate);
259
+ await this.save(aggregate);
213
260
  }
214
261
  }
215
262
 
@@ -229,26 +276,15 @@ class InMemoryRepository<
229
276
  return this.items.size;
230
277
  }
231
278
 
232
- /**
233
- * Clear all items (useful for test cleanup)
234
- */
235
279
  clear(): void {
236
280
  this.items.clear();
237
281
  }
238
282
 
239
- /**
240
- * Get all items as array (useful for debugging)
241
- */
242
283
  getAll(): TDomain[] {
243
284
  return Array.from(this.items.values());
244
285
  }
245
286
 
246
- /**
247
- * Get items count
248
- */
249
287
  size(): number {
250
288
  return this.items.size;
251
289
  }
252
290
  }
253
-
254
- export { User, Post, Comment, Address, InMemoryRepository };
@@ -1,52 +1,80 @@
1
- import { Address } from "./utils";
1
+ import { TagReference } from "./utils";
2
2
 
3
3
  describe("Value Object", () => {
4
4
  it("should create immutable value object", () => {
5
- const address = new Address({
6
- street: "Main St",
7
- city: "NYC",
8
- zipCode: "10001",
5
+ const tag = new TagReference({
6
+ tagId: "1",
7
+ name: "Tag 1",
9
8
  });
10
9
 
11
- expect(address.street).toBe("Main St");
12
- expect(address.city).toBe("NYC");
10
+ expect(tag.tagId).toBe("1");
11
+ expect(tag.name).toBe("Tag 1");
13
12
  });
14
13
 
15
14
  it("should compare value objects by value", () => {
16
- const address1 = new Address({
17
- street: "Main St",
18
- city: "NYC",
19
- zipCode: "10001",
15
+ const tag1 = new TagReference({
16
+ tagId: "1",
17
+ name: "Tag 1",
20
18
  });
21
19
 
22
- const address2 = new Address({
23
- street: "Main St",
24
- city: "NYC",
25
- zipCode: "10001",
20
+ const tag2 = new TagReference({
21
+ tagId: "1",
22
+ name: "Tag 1",
26
23
  });
27
24
 
28
- const address3 = new Address({
29
- street: "Broadway",
30
- city: "NYC",
31
- zipCode: "10001",
25
+ const tag3 = new TagReference({
26
+ tagId: "2",
27
+ name: "Tag 2",
32
28
  });
33
29
 
34
- expect(address1.equals(address2)).toBe(true);
35
- expect(address1.equals(address3)).toBe(false);
30
+ expect(tag1.equals(tag2)).toBe(true);
31
+ expect(tag1.equals(tag3)).toBe(false);
36
32
  });
37
33
 
38
34
  it("should convert value object to JSON", () => {
39
- const address = new Address({
40
- street: "Main St",
41
- city: "NYC",
42
- zipCode: "10001",
35
+ const tag = new TagReference({
36
+ name: "Tag 1",
37
+ tagId: "1",
43
38
  });
44
39
 
45
- const json = address.toJson();
40
+ const json = tag.toJson();
46
41
  expect(json).toEqual({
47
- street: "Main St",
48
- city: "NYC",
49
- zipCode: "10001",
42
+ name: "Tag 1",
43
+ tagId: "1",
44
+ });
45
+ });
46
+
47
+ describe("Value Object", () => {
48
+ it("should be immutable", () => {
49
+ const tag = new TagReference({
50
+ tagId: "1",
51
+ name: "Tag 1",
52
+ });
53
+
54
+ expect(tag.tagId).toBe("1");
55
+ expect(() => {
56
+ (tag as any).props.tagId = "2";
57
+ }).toThrow();
58
+ });
59
+
60
+ it("should compare by value", () => {
61
+ const tag1 = new TagReference({
62
+ tagId: "1",
63
+ name: "Tag 1",
64
+ });
65
+
66
+ const tag2 = new TagReference({
67
+ tagId: "1",
68
+ name: "Tag 1",
69
+ });
70
+
71
+ const tag3 = new TagReference({
72
+ tagId: "2",
73
+ name: "Tag 2",
74
+ });
75
+
76
+ expect(tag1.equals(tag2)).toBe(true);
77
+ expect(tag1.equals(tag3)).toBe(false);
50
78
  });
51
79
  });
52
80
  });
@@ -1,36 +0,0 @@
1
- import { HistoryEntry } from "./types";
2
- export declare class DeepProxy {
3
- private target;
4
- private path;
5
- private rootProxy?;
6
- private history;
7
- private subscribers;
8
- private originalValues;
9
- private trackedArraysCloned;
10
- private trackedArraysOriginal;
11
- private globalSubscribers;
12
- private updateInProgress;
13
- private pendingNotifications;
14
- constructor(target: any, path?: string, rootProxy?: DeepProxy | undefined);
15
- createProxy(): any;
16
- private scheduleNotification;
17
- private flushNotifications;
18
- private storeArrayState;
19
- private createArrayProxy;
20
- subscribe(path: string, callback: Function): void;
21
- unsubscribe(path: string, callback: Function): void;
22
- private notifySubscribers;
23
- private notifyGlobalSubscribers;
24
- private notifyArrayChange;
25
- private detectArrayChanges;
26
- private cloneArray;
27
- private deepClone;
28
- private getItemId;
29
- private hasChanged;
30
- private normalizeAndStringify;
31
- getHistory(): HistoryEntry[];
32
- getLastChangeForPath(path: string): HistoryEntry | undefined;
33
- clearHistory(): void;
34
- getTarget(): any;
35
- }
36
- //# sourceMappingURL=deep-proxy.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deep-proxy.d.ts","sourceRoot":"","sources":["../src/deep-proxy.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,qBAAa,SAAS;IAWlB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,SAAS,CAAC;IAZpB,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,mBAAmB,CAAiC;IAC5D,OAAO,CAAC,qBAAqB,CAAiC;IAC9D,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,oBAAoB,CAAyB;gBAG3C,MAAM,EAAE,GAAG,EACX,IAAI,GAAE,MAAW,EACjB,SAAS,CAAC,EAAE,SAAS,YAAA;IAK/B,WAAW,IAAI,GAAG;IA8FlB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,gBAAgB;IA8ExB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IA4DjD,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAanD,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,kBAAkB;IA2C1B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,qBAAqB;IAsB7B,UAAU,IAAI,YAAY,EAAE;IAI5B,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAS5D,YAAY,IAAI,IAAI;IAOpB,SAAS,IAAI,GAAG;CAGjB"}