@woltz/rich-domain 1.0.0 → 1.2.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/CHANGELOG.md +37 -86
- package/LICENSE +20 -20
- package/dist/base-entity.d.ts +1 -1
- package/dist/base-entity.d.ts.map +1 -1
- package/dist/base-entity.js +15 -19
- package/dist/base-entity.js.map +1 -1
- package/dist/constants.js +1 -4
- package/dist/constants.js.map +1 -1
- package/dist/criteria.d.ts +24 -14
- package/dist/criteria.d.ts.map +1 -1
- package/dist/criteria.js +154 -67
- package/dist/criteria.js.map +1 -1
- package/dist/deep-proxy.d.ts.map +1 -1
- package/dist/deep-proxy.js +19 -9
- package/dist/deep-proxy.js.map +1 -1
- package/dist/domain-event-bus.d.ts +5 -6
- package/dist/domain-event-bus.d.ts.map +1 -1
- package/dist/domain-event-bus.js +4 -17
- package/dist/domain-event-bus.js.map +1 -1
- package/dist/domain-event.d.ts +1 -31
- package/dist/domain-event.d.ts.map +1 -1
- package/dist/domain-event.js +4 -7
- package/dist/domain-event.js.map +1 -1
- package/dist/entity.d.ts +2 -2
- package/dist/entity.js +3 -8
- package/dist/entity.js.map +1 -1
- package/dist/exceptions.d.ts +251 -0
- package/dist/exceptions.d.ts.map +1 -0
- package/dist/exceptions.js +321 -0
- package/dist/exceptions.js.map +1 -0
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +14 -7
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -40
- package/dist/index.js.map +1 -1
- package/dist/mapper.js +1 -5
- package/dist/mapper.js.map +1 -1
- package/dist/paginated-result.d.ts.map +1 -1
- package/dist/paginated-result.js +17 -9
- package/dist/paginated-result.js.map +1 -1
- package/dist/repository/base-repository.js +4 -11
- package/dist/repository/base-repository.js.map +1 -1
- package/dist/repository/index.d.ts +1 -2
- package/dist/repository/index.d.ts.map +1 -1
- package/dist/repository/index.js +3 -26
- package/dist/repository/index.js.map +1 -1
- package/dist/repository/unit-of-work.d.ts +0 -11
- package/dist/repository/unit-of-work.d.ts.map +1 -1
- package/dist/repository/unit-of-work.js +2 -43
- package/dist/repository/unit-of-work.js.map +1 -1
- package/dist/types/criteria.d.ts +31 -7
- package/dist/types/criteria.d.ts.map +1 -1
- package/dist/types/criteria.js +1 -4
- package/dist/types/criteria.js.map +1 -1
- package/dist/types/domain-event.d.ts +32 -0
- package/dist/types/domain-event.d.ts.map +1 -0
- package/dist/types/domain-event.js +2 -0
- package/dist/types/domain-event.js.map +1 -0
- package/dist/types/domain.d.ts +2 -2
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/types/domain.js +1 -2
- package/dist/types/history-tracker.d.ts +1 -1
- package/dist/types/history-tracker.d.ts.map +1 -1
- package/dist/types/history-tracker.js +1 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -22
- package/dist/types/index.js.map +1 -1
- package/dist/types/standard-schema.js +1 -2
- package/dist/types/unit-of-work.js +1 -2
- package/dist/types/utils.js +1 -2
- package/dist/utils/criteria-operator-validation.d.ts +5 -0
- package/dist/utils/criteria-operator-validation.d.ts.map +1 -0
- package/dist/utils/criteria-operator-validation.js +143 -0
- package/dist/utils/criteria-operator-validation.js.map +1 -0
- package/dist/utils/helpers.d.ts +2 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +10 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/validation-error.js +3 -9
- package/dist/validation-error.js.map +1 -1
- package/dist/value-object.d.ts +1 -1
- package/dist/value-object.d.ts.map +1 -1
- package/dist/value-object.js +7 -14
- package/dist/value-object.js.map +1 -1
- package/eslint.config.js +9 -3
- package/jest.config.js +1 -1
- package/package.json +14 -20
- package/src/base-entity.ts +3 -3
- package/src/criteria.ts +268 -87
- package/src/deep-proxy.ts +50 -38
- package/src/domain-event-bus.ts +152 -166
- package/src/domain-event.ts +53 -90
- package/src/entity.ts +16 -16
- package/src/exceptions.ts +435 -0
- package/src/id.ts +107 -94
- package/src/index.ts +32 -8
- package/src/paginated-result.ts +15 -3
- package/src/repository/index.ts +1 -6
- package/src/repository/unit-of-work.ts +1 -44
- package/src/types/criteria.ts +95 -17
- package/src/types/domain-event.ts +38 -0
- package/src/types/domain.ts +2 -3
- package/src/types/history-tracker.ts +1 -1
- package/src/types/index.ts +1 -0
- package/src/utils/criteria-operator-validation.ts +171 -0
- package/src/utils/helpers.ts +6 -0
- package/src/validation-error.ts +97 -97
- package/src/value-object.ts +3 -6
- package/tests/criteria.test.ts +324 -1
- package/tests/domain-events.test.ts +431 -445
- package/tests/entity-validation.test.ts +1 -1
- package/tests/entity.test.ts +33 -33
- package/tests/repository.test.ts +4 -2
- package/tests/utils.ts +254 -151
- package/tests/value-object-validation.test.ts +0 -9
- package/tsconfig.json +2 -24
- package/.github/workflows/ci.yml +0 -40
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.vscode/settings.json +0 -3
- package/commitlint.config.js +0 -23
- package/dist/repository/in-memory-repository.d.ts +0 -50
- package/dist/repository/in-memory-repository.d.ts.map +0 -1
- package/dist/repository/in-memory-repository.js +0 -97
- package/dist/repository/in-memory-repository.js.map +0 -1
- package/src/repository/in-memory-repository.ts +0 -116
package/src/deep-proxy.ts
CHANGED
|
@@ -27,6 +27,8 @@ export class DeepProxy {
|
|
|
27
27
|
const handler: ProxyHandler<any> = {
|
|
28
28
|
get: (target, prop, receiver) => {
|
|
29
29
|
const value = Reflect.get(target, prop, receiver);
|
|
30
|
+
if (!this.rootProxy) throw new Error("Root proxy is required");
|
|
31
|
+
|
|
30
32
|
const currentPath = this.path
|
|
31
33
|
? `${this.path}.${String(prop)}`
|
|
32
34
|
: String(prop);
|
|
@@ -44,8 +46,8 @@ export class DeepProxy {
|
|
|
44
46
|
if (typeof value === "function") return value.bind(target);
|
|
45
47
|
|
|
46
48
|
if (Array.isArray(value)) {
|
|
47
|
-
if (!this.rootProxy
|
|
48
|
-
this.rootProxy
|
|
49
|
+
if (!this.rootProxy.trackedArraysCloned.has(currentPath)) {
|
|
50
|
+
this.rootProxy.storeArrayState(currentPath, value);
|
|
49
51
|
}
|
|
50
52
|
return this.createArrayProxy(value, currentPath);
|
|
51
53
|
}
|
|
@@ -62,16 +64,18 @@ export class DeepProxy {
|
|
|
62
64
|
const currentPath = this.path
|
|
63
65
|
? `${this.path}.${String(prop)}`
|
|
64
66
|
: String(prop);
|
|
67
|
+
if (!this.rootProxy) throw new Error("Root proxy is required");
|
|
68
|
+
|
|
65
69
|
const oldValue = Reflect.get(target, prop, receiver);
|
|
66
70
|
const isArrayAssignment = Array.isArray(newValue);
|
|
67
71
|
|
|
68
72
|
if (!isArrayAssignment && oldValue === newValue) return true;
|
|
69
73
|
|
|
70
|
-
if (!this.rootProxy
|
|
71
|
-
this.rootProxy
|
|
74
|
+
if (!this.rootProxy.originalValues.has(currentPath)) {
|
|
75
|
+
this.rootProxy.originalValues.set(currentPath, oldValue);
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
this.rootProxy
|
|
78
|
+
this.rootProxy.history.push({
|
|
75
79
|
path: currentPath,
|
|
76
80
|
previousValue: oldValue,
|
|
77
81
|
currentValue: newValue,
|
|
@@ -81,28 +85,28 @@ export class DeepProxy {
|
|
|
81
85
|
const result = Reflect.set(target, prop, newValue, receiver);
|
|
82
86
|
|
|
83
87
|
if (isArrayAssignment) {
|
|
84
|
-
if (!this.rootProxy
|
|
88
|
+
if (!this.rootProxy.trackedArraysCloned.has(currentPath)) {
|
|
85
89
|
if (Array.isArray(oldValue)) {
|
|
86
|
-
this.rootProxy
|
|
90
|
+
this.rootProxy.storeArrayState(currentPath, oldValue);
|
|
87
91
|
} else {
|
|
88
|
-
this.rootProxy
|
|
92
|
+
this.rootProxy.storeArrayState(currentPath, []);
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
|
-
this.rootProxy
|
|
95
|
+
this.rootProxy.scheduleNotification(() =>
|
|
92
96
|
this.rootProxy!.notifyArrayChange(currentPath, newValue)
|
|
93
97
|
);
|
|
94
98
|
} else {
|
|
95
|
-
this.rootProxy
|
|
99
|
+
this.rootProxy.scheduleNotification(() =>
|
|
96
100
|
this.rootProxy!.notifySubscribers(currentPath, oldValue, newValue)
|
|
97
101
|
);
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
// Notify global subscribers
|
|
101
|
-
this.rootProxy
|
|
105
|
+
this.rootProxy.scheduleNotification(() =>
|
|
102
106
|
this.rootProxy!.notifyGlobalSubscribers()
|
|
103
107
|
);
|
|
104
108
|
|
|
105
|
-
this.rootProxy
|
|
109
|
+
this.rootProxy.flushNotifications();
|
|
106
110
|
|
|
107
111
|
return result;
|
|
108
112
|
},
|
|
@@ -140,6 +144,8 @@ export class DeepProxy {
|
|
|
140
144
|
return new Proxy(array, {
|
|
141
145
|
get(target, prop, receiver) {
|
|
142
146
|
const value = Reflect.get(target, prop, receiver);
|
|
147
|
+
if (!self.rootProxy) throw new Error("Root proxy is required");
|
|
148
|
+
|
|
143
149
|
if (typeof value === "function") {
|
|
144
150
|
const mutatingMethods = [
|
|
145
151
|
"push",
|
|
@@ -153,23 +159,24 @@ export class DeepProxy {
|
|
|
153
159
|
if (mutatingMethods.includes(String(prop))) {
|
|
154
160
|
return function (...args: any[]) {
|
|
155
161
|
// Capture state before mutation
|
|
162
|
+
if (!self.rootProxy) throw new Error("Root proxy is required");
|
|
156
163
|
const oldArray = target.slice();
|
|
157
164
|
|
|
158
165
|
const result = value.apply(target, args);
|
|
159
166
|
|
|
160
167
|
// Add to history
|
|
161
|
-
self.rootProxy
|
|
168
|
+
self.rootProxy.history.push({
|
|
162
169
|
path: path,
|
|
163
170
|
previousValue: oldArray,
|
|
164
171
|
currentValue: target.slice(),
|
|
165
172
|
timestamp: Date.now(),
|
|
166
173
|
});
|
|
167
174
|
|
|
168
|
-
if (!self.rootProxy
|
|
169
|
-
self.rootProxy
|
|
175
|
+
if (!self.rootProxy.trackedArraysCloned.has(path)) {
|
|
176
|
+
self.rootProxy.storeArrayState(path, target);
|
|
170
177
|
}
|
|
171
|
-
self.rootProxy
|
|
172
|
-
self.rootProxy
|
|
178
|
+
self.rootProxy.notifyArrayChange(path, target);
|
|
179
|
+
self.rootProxy.notifyGlobalSubscribers();
|
|
173
180
|
return result;
|
|
174
181
|
};
|
|
175
182
|
}
|
|
@@ -184,24 +191,25 @@ export class DeepProxy {
|
|
|
184
191
|
},
|
|
185
192
|
set(target, prop, newValue, receiver) {
|
|
186
193
|
if (!isNaN(Number(prop))) {
|
|
194
|
+
if (!self.rootProxy) throw new Error("Root proxy is required");
|
|
187
195
|
// Capture state before change
|
|
188
196
|
const oldArray = target.slice();
|
|
189
197
|
|
|
190
198
|
const result = Reflect.set(target, prop, newValue, receiver);
|
|
191
199
|
|
|
192
200
|
// Add to history
|
|
193
|
-
self.rootProxy
|
|
201
|
+
self.rootProxy.history.push({
|
|
194
202
|
path: path,
|
|
195
203
|
previousValue: oldArray,
|
|
196
204
|
currentValue: target.slice(),
|
|
197
205
|
timestamp: Date.now(),
|
|
198
206
|
});
|
|
199
207
|
|
|
200
|
-
if (!self.rootProxy
|
|
201
|
-
self.rootProxy
|
|
208
|
+
if (!self.rootProxy.trackedArraysCloned.has(path)) {
|
|
209
|
+
self.rootProxy.storeArrayState(path, target);
|
|
202
210
|
}
|
|
203
|
-
self.rootProxy
|
|
204
|
-
self.rootProxy
|
|
211
|
+
self.rootProxy.notifyArrayChange(path, target);
|
|
212
|
+
self.rootProxy.notifyGlobalSubscribers();
|
|
205
213
|
return result;
|
|
206
214
|
}
|
|
207
215
|
return Reflect.set(target, prop, newValue, receiver);
|
|
@@ -210,21 +218,24 @@ export class DeepProxy {
|
|
|
210
218
|
}
|
|
211
219
|
|
|
212
220
|
subscribe(path: string, callback: Function): void {
|
|
221
|
+
if (!this.rootProxy) throw new Error("Root proxy is required");
|
|
222
|
+
|
|
213
223
|
if (path === "*") {
|
|
214
|
-
this.rootProxy
|
|
224
|
+
this.rootProxy.globalSubscribers.add(callback);
|
|
215
225
|
return;
|
|
216
226
|
}
|
|
217
227
|
|
|
218
|
-
if (!this.rootProxy
|
|
219
|
-
this.rootProxy
|
|
228
|
+
if (!this.rootProxy.subscribers.has(path)) {
|
|
229
|
+
this.rootProxy.subscribers.set(path, new Set());
|
|
220
230
|
}
|
|
221
|
-
this.rootProxy!.subscribers.get(path)!.add(callback);
|
|
222
231
|
|
|
223
|
-
|
|
232
|
+
this.rootProxy.subscribers.get(path)!.add(callback);
|
|
233
|
+
|
|
234
|
+
const actualValue = this.rootProxy.target[path];
|
|
224
235
|
const isArray = Array.isArray(actualValue);
|
|
225
236
|
|
|
226
237
|
// Fire callback immediately if there's a historical change for this path
|
|
227
|
-
const lastChange = this.rootProxy
|
|
238
|
+
const lastChange = this.rootProxy.getLastChangeForPath(path);
|
|
228
239
|
if (lastChange) {
|
|
229
240
|
if (isArray) {
|
|
230
241
|
// For arrays, we need to detect changes and fire with toCreate/toUpdate/toDelete
|
|
@@ -236,14 +247,14 @@ export class DeepProxy {
|
|
|
236
247
|
: actualValue;
|
|
237
248
|
|
|
238
249
|
// Store the initial state before the change for comparison
|
|
239
|
-
if (!this.rootProxy
|
|
240
|
-
this.rootProxy
|
|
250
|
+
if (!this.rootProxy.trackedArraysCloned.has(path)) {
|
|
251
|
+
this.rootProxy.storeArrayState(path, previousArray);
|
|
241
252
|
}
|
|
242
253
|
|
|
243
254
|
// Detect changes between previous and current
|
|
244
|
-
const changes = this.rootProxy
|
|
245
|
-
this.rootProxy
|
|
246
|
-
this.rootProxy
|
|
255
|
+
const changes = this.rootProxy.detectArrayChanges(
|
|
256
|
+
this.rootProxy.trackedArraysCloned.get(path) ||
|
|
257
|
+
this.rootProxy.cloneArray(previousArray),
|
|
247
258
|
previousArray,
|
|
248
259
|
currentArray
|
|
249
260
|
);
|
|
@@ -251,7 +262,7 @@ export class DeepProxy {
|
|
|
251
262
|
callback({ ...changes, path });
|
|
252
263
|
|
|
253
264
|
// Update tracked state to current
|
|
254
|
-
this.rootProxy
|
|
265
|
+
this.rootProxy.storeArrayState(path, currentArray);
|
|
255
266
|
} else {
|
|
256
267
|
// For non-arrays, use the simple property change format
|
|
257
268
|
callback({
|
|
@@ -260,19 +271,20 @@ export class DeepProxy {
|
|
|
260
271
|
path: lastChange.path,
|
|
261
272
|
});
|
|
262
273
|
}
|
|
263
|
-
} else if (isArray && !this.rootProxy
|
|
274
|
+
} else if (isArray && !this.rootProxy.trackedArraysCloned.has(path)) {
|
|
264
275
|
// No historical changes, just store initial state
|
|
265
|
-
this.rootProxy
|
|
276
|
+
this.rootProxy.storeArrayState(path, actualValue);
|
|
266
277
|
}
|
|
267
278
|
}
|
|
268
279
|
|
|
269
280
|
unsubscribe(path: string, callback: Function): void {
|
|
281
|
+
if (!this.rootProxy) throw new Error("Root proxy is required");
|
|
270
282
|
if (path === "*") {
|
|
271
|
-
this.rootProxy
|
|
283
|
+
this.rootProxy.globalSubscribers.delete(callback);
|
|
272
284
|
return;
|
|
273
285
|
}
|
|
274
286
|
|
|
275
|
-
const subs = this.rootProxy
|
|
287
|
+
const subs = this.rootProxy.subscribers.get(path);
|
|
276
288
|
if (subs) {
|
|
277
289
|
subs.delete(callback);
|
|
278
290
|
}
|
package/src/domain-event-bus.ts
CHANGED
|
@@ -1,166 +1,152 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
`Error handling event ${event.eventName}:`,
|
|
154
|
-
error
|
|
155
|
-
);
|
|
156
|
-
// Don't throw - we don't want one handler failure to break others
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Convenience function to get the event bus instance
|
|
163
|
-
*/
|
|
164
|
-
export function getEventBus(): DomainEventBus {
|
|
165
|
-
return DomainEventBus.getInstance();
|
|
166
|
-
}
|
|
1
|
+
import { IDomainEvent, DomainEventHandler, IDomainEventHandler } from ".";
|
|
2
|
+
|
|
3
|
+
type EventConstructor<T extends IDomainEvent = IDomainEvent> = new (
|
|
4
|
+
...args: any[]
|
|
5
|
+
) => T;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Domain Event Bus - Singleton pattern for event pub/sub
|
|
9
|
+
*/
|
|
10
|
+
export class DomainEventBus {
|
|
11
|
+
private static instance: DomainEventBus;
|
|
12
|
+
private handlers: Map<
|
|
13
|
+
string,
|
|
14
|
+
Set<DomainEventHandler<any> | IDomainEventHandler<any>>
|
|
15
|
+
> = new Map();
|
|
16
|
+
private wildcardHandlers: Set<
|
|
17
|
+
DomainEventHandler<any> | IDomainEventHandler<any>
|
|
18
|
+
> = new Set();
|
|
19
|
+
|
|
20
|
+
private constructor() {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the singleton instance
|
|
24
|
+
*/
|
|
25
|
+
static getInstance(): DomainEventBus {
|
|
26
|
+
if (!DomainEventBus.instance) {
|
|
27
|
+
DomainEventBus.instance = new DomainEventBus();
|
|
28
|
+
}
|
|
29
|
+
return DomainEventBus.instance;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Subscribe to a specific event type
|
|
34
|
+
*/
|
|
35
|
+
subscribe<T extends IDomainEvent>(props: {
|
|
36
|
+
event: EventConstructor<T> | string;
|
|
37
|
+
handler: DomainEventHandler<T> | IDomainEventHandler<T>;
|
|
38
|
+
}): void {
|
|
39
|
+
const { event, handler } = props;
|
|
40
|
+
const eventName = typeof event === "string" ? event : event.name;
|
|
41
|
+
|
|
42
|
+
if (!this.handlers.has(eventName)) {
|
|
43
|
+
this.handlers.set(eventName, new Set());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.handlers.get(eventName)!.add(handler);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to all events (wildcard)
|
|
51
|
+
*/
|
|
52
|
+
subscribeAll(
|
|
53
|
+
handler:
|
|
54
|
+
| DomainEventHandler<IDomainEvent>
|
|
55
|
+
| IDomainEventHandler<IDomainEvent>
|
|
56
|
+
): void {
|
|
57
|
+
this.wildcardHandlers.add(handler);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Unsubscribe from a specific event type
|
|
62
|
+
*/
|
|
63
|
+
unsubscribe<T extends IDomainEvent>(
|
|
64
|
+
eventType: EventConstructor<T> | string,
|
|
65
|
+
handler: DomainEventHandler<T> | IDomainEventHandler<T>
|
|
66
|
+
): void {
|
|
67
|
+
const eventName =
|
|
68
|
+
typeof eventType === "string" ? eventType : eventType.name;
|
|
69
|
+
const handlers = this.handlers.get(eventName);
|
|
70
|
+
|
|
71
|
+
if (handlers) {
|
|
72
|
+
handlers.delete(handler);
|
|
73
|
+
if (handlers.size === 0) {
|
|
74
|
+
this.handlers.delete(eventName);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Unsubscribe from all events
|
|
81
|
+
*/
|
|
82
|
+
unsubscribeAll(
|
|
83
|
+
handler:
|
|
84
|
+
| DomainEventHandler<IDomainEvent>
|
|
85
|
+
| IDomainEventHandler<IDomainEvent>
|
|
86
|
+
): void {
|
|
87
|
+
this.wildcardHandlers.delete(handler);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Publish a single event
|
|
92
|
+
*/
|
|
93
|
+
async publish<T extends IDomainEvent>(event: T): Promise<void> {
|
|
94
|
+
const eventName = event.eventName;
|
|
95
|
+
const handlers = this.handlers.get(eventName) || new Set();
|
|
96
|
+
|
|
97
|
+
// Execute specific handlers
|
|
98
|
+
const specificPromises = Array.from(handlers).map((handler) =>
|
|
99
|
+
this.executeHandler(handler, event)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Execute wildcard handlers
|
|
103
|
+
const wildcardPromises = Array.from(this.wildcardHandlers).map((handler) =>
|
|
104
|
+
this.executeHandler(handler, event)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
await Promise.all([...specificPromises, ...wildcardPromises]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Publish multiple events
|
|
112
|
+
*/
|
|
113
|
+
async publishAll(events: IDomainEvent[]): Promise<void> {
|
|
114
|
+
await Promise.all(events.map((event) => this.publish(event)));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Clear all handlers (useful for testing)
|
|
119
|
+
*/
|
|
120
|
+
clearAllHandlers(): void {
|
|
121
|
+
this.handlers.clear();
|
|
122
|
+
this.wildcardHandlers.clear();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get count of handlers for an event type
|
|
127
|
+
*/
|
|
128
|
+
getHandlerCount(eventType: EventConstructor | string): number {
|
|
129
|
+
const eventName =
|
|
130
|
+
typeof eventType === "string" ? eventType : eventType.name;
|
|
131
|
+
return this.handlers.get(eventName)?.size || 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Execute a handler (function or class)
|
|
136
|
+
*/
|
|
137
|
+
private async executeHandler(
|
|
138
|
+
handler: DomainEventHandler<any> | IDomainEventHandler<any>,
|
|
139
|
+
event: IDomainEvent
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
try {
|
|
142
|
+
if (typeof handler === "function") {
|
|
143
|
+
await handler(event);
|
|
144
|
+
} else {
|
|
145
|
+
await handler.handle(event);
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error(`Error handling event ${event.eventName}:`, error);
|
|
149
|
+
// Don't throw - we don't want one handler failure to break others
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|