@vytches/ddd-aggregates 0.26.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/LICENSE +21 -0
- package/LLMGUIDE.md +185 -0
- package/README.md +1393 -0
- package/dist/aggregate-errors.d.ts +58 -0
- package/dist/aggregate-interfaces.d.ts +398 -0
- package/dist/capabilities/audit-capability.d.ts +95 -0
- package/dist/capabilities/event-sourcing-capability.d.ts +79 -0
- package/dist/capabilities/index.d.ts +4 -0
- package/dist/capabilities/snapshot-capability.d.ts +113 -0
- package/dist/capabilities/versioning-capability.d.ts +93 -0
- package/dist/core/aggregate-root.builder.d.ts +110 -0
- package/dist/core/aggregate-root.d.ts +201 -0
- package/dist/core/aggregate-utilities.d.ts +233 -0
- package/dist/core/entity.d.ts +76 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1283 -0
- package/package.json +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1283 @@
|
|
|
1
|
+
import { Logger as _ } from "/home/runner/work/ddd/ddd/packages/logging/dist/index.js";
|
|
2
|
+
import { IDomainError as I, DomainErrorCode as c } from "/home/runner/work/ddd/ddd/packages/domain-primitives/dist/index.js";
|
|
3
|
+
function $() {
|
|
4
|
+
return globalThis.crypto.randomUUID();
|
|
5
|
+
}
|
|
6
|
+
function N(s, t, e) {
|
|
7
|
+
return {
|
|
8
|
+
eventName: s,
|
|
9
|
+
payload: t,
|
|
10
|
+
metadata: {
|
|
11
|
+
eventId: $(),
|
|
12
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
13
|
+
...e
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
class p {
|
|
18
|
+
/**
|
|
19
|
+
* Checks if this capability is of a specific type
|
|
20
|
+
*/
|
|
21
|
+
isType(t) {
|
|
22
|
+
return this.type === t;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Static type getter for compile-time type extraction
|
|
26
|
+
* Each capability must override this
|
|
27
|
+
*/
|
|
28
|
+
static get capabilityType() {
|
|
29
|
+
throw new Error("Capability subclass must override static capabilityType getter");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class D {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.capabilities = /* @__PURE__ */ new Map();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Register a capability
|
|
38
|
+
*/
|
|
39
|
+
register(t) {
|
|
40
|
+
return this.capabilities.set(t.type, t), this;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get a capability by its constructor
|
|
44
|
+
*/
|
|
45
|
+
get(t) {
|
|
46
|
+
const e = t.capabilityType;
|
|
47
|
+
return this.capabilities.get(e);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get a capability by its type string
|
|
51
|
+
*/
|
|
52
|
+
getByType(t) {
|
|
53
|
+
return this.capabilities.get(t);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if a capability exists
|
|
57
|
+
*/
|
|
58
|
+
has(t) {
|
|
59
|
+
const e = t.capabilityType;
|
|
60
|
+
return this.capabilities.has(e);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if a capability exists by type
|
|
64
|
+
*/
|
|
65
|
+
hasByType(t) {
|
|
66
|
+
return this.capabilities.has(t);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Remove a capability
|
|
70
|
+
*/
|
|
71
|
+
remove(t) {
|
|
72
|
+
const e = t.capabilityType;
|
|
73
|
+
return this.capabilities.delete(e);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Remove a capability by type
|
|
77
|
+
*/
|
|
78
|
+
removeByType(t) {
|
|
79
|
+
return this.capabilities.delete(t);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get all registered capabilities
|
|
83
|
+
*/
|
|
84
|
+
getAll() {
|
|
85
|
+
return Array.from(this.capabilities.values());
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get all capability types
|
|
89
|
+
*/
|
|
90
|
+
getTypes() {
|
|
91
|
+
return Array.from(this.capabilities.keys());
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Clear all capabilities
|
|
95
|
+
*/
|
|
96
|
+
clear() {
|
|
97
|
+
this.capabilities.clear();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the number of registered capabilities
|
|
101
|
+
*/
|
|
102
|
+
get size() {
|
|
103
|
+
return this.capabilities.size;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
class u {
|
|
107
|
+
constructor(t, e = "text") {
|
|
108
|
+
this.value = t, this.type = e;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Returns the stored identifier value
|
|
112
|
+
* @returns The identifier value
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const id = EntityId.fromText('user-123');
|
|
116
|
+
* const value = id.getValue(); // Returns: 'user-123'
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
getValue() {
|
|
120
|
+
return this.value;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Returns the identifier type (text, uuid, integer, bigint)
|
|
124
|
+
* @returns The identifier type
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* const id = EntityId.fromUUID('550e8400-e29b-41d4-a716-446655440000');
|
|
128
|
+
* const type = id.getType(); // Returns: 'uuid'
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
getType() {
|
|
132
|
+
return this.type;
|
|
133
|
+
}
|
|
134
|
+
validate(t) {
|
|
135
|
+
return t != null && t !== void 0;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Compares this EntityId with another for equality
|
|
139
|
+
* @returns True if entities have same value and type
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const id1 = EntityId.fromText('user-123');
|
|
143
|
+
* const id2 = EntityId.fromText('user-123');
|
|
144
|
+
* const isEqual = id1.equals(id2); // Returns: true
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
equals(t) {
|
|
148
|
+
return t.getValue() === this.value && t.getType() === this.type;
|
|
149
|
+
}
|
|
150
|
+
isType(t) {
|
|
151
|
+
return this.type === t;
|
|
152
|
+
}
|
|
153
|
+
toString() {
|
|
154
|
+
return String(this.value);
|
|
155
|
+
}
|
|
156
|
+
toJSON() {
|
|
157
|
+
return JSON.stringify({
|
|
158
|
+
value: this.value,
|
|
159
|
+
type: this.type
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Primary factory method - creates a new EntityId with a randomly generated UUID
|
|
164
|
+
* @returns New EntityId with random UUID
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* const id = EntityId.create();
|
|
168
|
+
* console.log(id.getValue()); // Returns: '550e8400-e29b-41d4-a716-446655440000'
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
static create() {
|
|
172
|
+
const t = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (e) => {
|
|
173
|
+
const i = Math.random() * 16 | 0;
|
|
174
|
+
return (e === "x" ? i : i & 3 | 8).toString(16);
|
|
175
|
+
});
|
|
176
|
+
return new u(t, "uuid");
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* @deprecated Use `fromText()` instead. Will be removed in next major version.
|
|
180
|
+
*/
|
|
181
|
+
static createText(t) {
|
|
182
|
+
return new u(t, "text");
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* @deprecated Use `fromUUID()` instead. Will be removed in next major version.
|
|
186
|
+
*/
|
|
187
|
+
static createUuid(t) {
|
|
188
|
+
return new u(t, "uuid");
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* @deprecated Use `fromInteger()` instead. Will be removed in next major version.
|
|
192
|
+
*/
|
|
193
|
+
static createInteger(t) {
|
|
194
|
+
return new u(t.toString(), "integer");
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* @deprecated Use `fromBigInt()` instead. Will be removed in next major version.
|
|
198
|
+
*/
|
|
199
|
+
static createBigInt(t) {
|
|
200
|
+
const e = typeof t == "bigint" ? t.toString() : t;
|
|
201
|
+
return new u(e, "bigint");
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* @deprecated Use `create()` instead. Will be removed in next major version.
|
|
205
|
+
* Creates a new EntityId with a randomly generated UUID
|
|
206
|
+
* @returns New EntityId with random UUID
|
|
207
|
+
*/
|
|
208
|
+
static createWithRandomUUID() {
|
|
209
|
+
return u.create();
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Creates an EntityId from a UUID string with validation
|
|
213
|
+
* @returns EntityId from UUID string
|
|
214
|
+
* @example
|
|
215
|
+
* ```typescript
|
|
216
|
+
* const id = EntityId.fromUUID('550e8400-e29b-41d4-a716-446655440000');
|
|
217
|
+
* console.log(id.getType()); // Returns: 'uuid'
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
static fromUUID(t) {
|
|
221
|
+
if (!t || typeof t != "string")
|
|
222
|
+
throw new Error("entity identifier must be provided");
|
|
223
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(t))
|
|
224
|
+
throw new Error("entity identifier must be valid UUID");
|
|
225
|
+
return new u(t, "uuid");
|
|
226
|
+
}
|
|
227
|
+
static fromInteger(t) {
|
|
228
|
+
if (typeof t != "number" || t < 0 || !Number.isInteger(t))
|
|
229
|
+
throw new Error("entity identifier must be a non-negative integer");
|
|
230
|
+
return new u(t.toString(), "integer");
|
|
231
|
+
}
|
|
232
|
+
static fromBigInt(t) {
|
|
233
|
+
const e = typeof t == "bigint" ? t.toString() : t;
|
|
234
|
+
if (!e || typeof e != "string")
|
|
235
|
+
throw new Error("entity identifier must be a valid bigint");
|
|
236
|
+
return new u(e, "bigint");
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Creates an EntityId from a text string with validation
|
|
240
|
+
* @returns EntityId from text string
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* const id = EntityId.fromText('user-123');
|
|
244
|
+
* console.log(id.getValue()); // Returns: 'user-123'
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
static fromText(t) {
|
|
248
|
+
if (!t || typeof t != "string")
|
|
249
|
+
throw new Error("entity identifier must be provided");
|
|
250
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(t))
|
|
251
|
+
throw new Error("entity identifier contains invalid characters");
|
|
252
|
+
return new u(t, "text");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
class o {
|
|
256
|
+
constructor(t, e, i) {
|
|
257
|
+
this._isSuccess = t, this._value = e, this._error = i;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if the result is successful
|
|
261
|
+
*/
|
|
262
|
+
get isSuccess() {
|
|
263
|
+
return this._isSuccess;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Check if the result is a failure
|
|
267
|
+
*/
|
|
268
|
+
get isFailure() {
|
|
269
|
+
return !this._isSuccess;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get the success value
|
|
273
|
+
* @throws Error if the result is a failure
|
|
274
|
+
*/
|
|
275
|
+
get value() {
|
|
276
|
+
if (!this._isSuccess)
|
|
277
|
+
throw new Error("Cannot get value of a failure result");
|
|
278
|
+
return this._value;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get the error value
|
|
282
|
+
* @throws Error if the result is successful
|
|
283
|
+
*/
|
|
284
|
+
get error() {
|
|
285
|
+
if (this._isSuccess)
|
|
286
|
+
throw new Error("Cannot get error of a success result");
|
|
287
|
+
return this._error;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Create a successful result
|
|
291
|
+
* @param value - The success value (required — use Result.empty() for void operations)
|
|
292
|
+
*/
|
|
293
|
+
static ok(t) {
|
|
294
|
+
return new o(!0, t);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Create a successful void result (no value).
|
|
298
|
+
*/
|
|
299
|
+
static empty() {
|
|
300
|
+
return new o(!0, void 0);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Create a failure result
|
|
304
|
+
* @param error - The error value
|
|
305
|
+
*/
|
|
306
|
+
static fail(t) {
|
|
307
|
+
return new o(!1, void 0, t);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Try to execute a function and wrap the result
|
|
311
|
+
* @param fn - The function to try
|
|
312
|
+
* @returns A Result containing the function result or error
|
|
313
|
+
*/
|
|
314
|
+
static try(t) {
|
|
315
|
+
try {
|
|
316
|
+
return o.ok(t());
|
|
317
|
+
} catch (e) {
|
|
318
|
+
return o.fail(e instanceof Error ? e : new Error(String(e)));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Transform the success value
|
|
323
|
+
* @param fn - The transformation function
|
|
324
|
+
*/
|
|
325
|
+
map(t) {
|
|
326
|
+
return this.isFailure ? o.fail(this.error) : o.ok(t(this.value));
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Transform the success value with a function that returns a Result
|
|
330
|
+
* @param fn - The transformation function
|
|
331
|
+
*/
|
|
332
|
+
flatMap(t) {
|
|
333
|
+
return this.isFailure ? o.fail(this.error) : t(this.value);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Apply a function based on success or failure
|
|
337
|
+
* @param onSuccess - Function to call on success
|
|
338
|
+
* @param onFailure - Function to call on failure
|
|
339
|
+
*/
|
|
340
|
+
match(t, e) {
|
|
341
|
+
return this.isSuccess ? t(this.value) : e(this.error);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Apply a side effect if the result is successful
|
|
345
|
+
* @param fn - The side effect function
|
|
346
|
+
*/
|
|
347
|
+
tap(t) {
|
|
348
|
+
return this.isSuccess && t(this.value), this;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Apply a side effect if the result is a failure
|
|
352
|
+
* @param fn - The side effect function
|
|
353
|
+
*/
|
|
354
|
+
tapError(t) {
|
|
355
|
+
return this.isFailure && t(this.error), this;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Transform the error value if the result is a failure
|
|
359
|
+
* @param fn - The error transformation function
|
|
360
|
+
*/
|
|
361
|
+
mapError(t) {
|
|
362
|
+
return this.isSuccess ? o.ok(this.value) : o.fail(t(this.error));
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Try to execute an async function and wrap the result
|
|
366
|
+
* @param fn - The async function to try
|
|
367
|
+
* @returns A Promise of a Result containing the function result or error
|
|
368
|
+
*/
|
|
369
|
+
static async tryAsync(t) {
|
|
370
|
+
try {
|
|
371
|
+
const e = await t();
|
|
372
|
+
return o.ok(e);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
return o.fail(e instanceof Error ? e : new Error(String(e)));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Transform the success value with an async function
|
|
379
|
+
* @param fn - The async transformation function
|
|
380
|
+
*/
|
|
381
|
+
async mapAsync(t) {
|
|
382
|
+
if (this.isFailure)
|
|
383
|
+
return o.fail(this.error);
|
|
384
|
+
try {
|
|
385
|
+
const e = await t(this.value);
|
|
386
|
+
return o.ok(e);
|
|
387
|
+
} catch (e) {
|
|
388
|
+
const i = e instanceof Error ? e : new Error(String(e));
|
|
389
|
+
return o.fail(i);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Transform the success value with an async function that returns a Result
|
|
394
|
+
* @param fn - The async transformation function
|
|
395
|
+
*/
|
|
396
|
+
async flatMapAsync(t) {
|
|
397
|
+
return this.isFailure ? o.fail(this.error) : await t(this.value);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
class f extends p {
|
|
401
|
+
constructor() {
|
|
402
|
+
super(...arguments), this.type = "versioning", this.upcasters = /* @__PURE__ */ new Map();
|
|
403
|
+
}
|
|
404
|
+
static get capabilityType() {
|
|
405
|
+
return "versioning";
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* @param {unknown} aggregate - Aggregate to attach this capability to
|
|
409
|
+
*/
|
|
410
|
+
attach(t) {
|
|
411
|
+
this.aggregate = t;
|
|
412
|
+
}
|
|
413
|
+
detach() {
|
|
414
|
+
this.aggregate = void 0, this.upcasters.clear();
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* @param {string} eventType - Type of event to register upcaster for
|
|
418
|
+
* @param {number} sourceVersion - Source version number to upcast from
|
|
419
|
+
* @param {IEventUpcaster<TFrom, TTo>} upcaster - Upcaster instance to transform events
|
|
420
|
+
*/
|
|
421
|
+
registerUpcaster(t, e, i) {
|
|
422
|
+
this.upcasters.has(t) || this.upcasters.set(t, /* @__PURE__ */ new Map()), this.upcasters.get(t).set(e, i);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* @param {IDomainEvent} event - Event to process with version handling
|
|
426
|
+
* @param {Map<string, IAggregateEventHandler>} handlers - Map of event handlers
|
|
427
|
+
*/
|
|
428
|
+
handleVersionedEvent(t, e) {
|
|
429
|
+
const i = t.metadata?.version || 1, r = t.metadata?.targetVersion || i;
|
|
430
|
+
let a = t;
|
|
431
|
+
if (i < r && this.upcasters.has(t.eventName)) {
|
|
432
|
+
const g = this.upcasters.get(t.eventName), d = _.forContext("VersioningCapability");
|
|
433
|
+
for (let l = i; l < r; l++) {
|
|
434
|
+
const y = g.get(l);
|
|
435
|
+
if (!y) {
|
|
436
|
+
d.warn(
|
|
437
|
+
"Missing upcaster for event version — event will be processed without upcast",
|
|
438
|
+
{
|
|
439
|
+
eventName: t.eventName,
|
|
440
|
+
fromVersion: l,
|
|
441
|
+
toVersion: l + 1
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const T = y.upcast(a.payload, a.metadata);
|
|
447
|
+
a = {
|
|
448
|
+
...a,
|
|
449
|
+
payload: T,
|
|
450
|
+
metadata: {
|
|
451
|
+
...a.metadata,
|
|
452
|
+
version: l + 1
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const h = e.get(a.eventName);
|
|
458
|
+
h && h(a.payload, a.metadata);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* @returns {string[]} Array of event types that have registered upcasters
|
|
462
|
+
*/
|
|
463
|
+
getRegisteredEventTypes() {
|
|
464
|
+
return Array.from(this.upcasters.keys());
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* @param {string} eventType - Event type to check
|
|
468
|
+
* @param {number} version - Version number to check for upcaster
|
|
469
|
+
* @returns {boolean} True if upcaster exists for specified event type and version
|
|
470
|
+
*/
|
|
471
|
+
hasUpcaster(t, e) {
|
|
472
|
+
return this.upcasters.get(t)?.has(e) || !1;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* @param {string} eventType - Event type to get upcasters for
|
|
476
|
+
* @returns {Map<number, IEventUpcaster> | undefined} Map of upcasters by version or undefined
|
|
477
|
+
*/
|
|
478
|
+
getUpcastersForType(t) {
|
|
479
|
+
return this.upcasters.get(t);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* @param {string} eventType - Event type to clear upcasters for
|
|
483
|
+
*/
|
|
484
|
+
clearUpcastersForType(t) {
|
|
485
|
+
this.upcasters.delete(t);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* @returns {number} Total count of all registered upcasters across all event types
|
|
489
|
+
*/
|
|
490
|
+
getTotalUpcasterCount() {
|
|
491
|
+
let t = 0;
|
|
492
|
+
for (const e of this.upcasters.values())
|
|
493
|
+
t += e.size;
|
|
494
|
+
return t;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
class v {
|
|
498
|
+
constructor(t) {
|
|
499
|
+
this._version = 0, this._initialVersion = 0, this._domainEvents = [], this._eventHandlers = /* @__PURE__ */ new Map(), this._capabilities = new D(), this._id = t.id, this._version = t.version || 0, this._initialVersion = t.version || 0, this._maxEvents = t.maxEvents;
|
|
500
|
+
}
|
|
501
|
+
// ==========================================
|
|
502
|
+
// CORE AGGREGATE FUNCTIONALITY
|
|
503
|
+
// ==========================================
|
|
504
|
+
/**
|
|
505
|
+
* Get the unique identifier of this aggregate
|
|
506
|
+
*/
|
|
507
|
+
getId() {
|
|
508
|
+
return this._id;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
*
|
|
512
|
+
* @returns {number} Current version number for optimistic locking
|
|
513
|
+
*/
|
|
514
|
+
getVersion() {
|
|
515
|
+
return this._version;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
*
|
|
519
|
+
* @returns {number} Initial version when aggregate was loaded
|
|
520
|
+
*/
|
|
521
|
+
getInitialVersion() {
|
|
522
|
+
return this._initialVersion;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
*
|
|
526
|
+
* @returns {boolean} True if aggregate has uncommitted domain events
|
|
527
|
+
*/
|
|
528
|
+
hasChanges() {
|
|
529
|
+
return this._domainEvents.length > 0;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
*
|
|
533
|
+
* @returns {ReadonlyArray<IDomainEvent>} Array of uncommitted domain events
|
|
534
|
+
*/
|
|
535
|
+
getDomainEvents() {
|
|
536
|
+
return [...this._domainEvents];
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Clear all uncommitted domain events and update initial version
|
|
540
|
+
*/
|
|
541
|
+
commit() {
|
|
542
|
+
this._domainEvents = [], this._initialVersion = this._version;
|
|
543
|
+
}
|
|
544
|
+
// ==========================================
|
|
545
|
+
// EVENT HANDLING - Type-safe approach
|
|
546
|
+
// ==========================================
|
|
547
|
+
/**
|
|
548
|
+
* @param {string} eventType - Event type to register handler for
|
|
549
|
+
* @param {IAggregateEventHandler<T>} handler - Event handler function
|
|
550
|
+
* @returns {this} The aggregate instance for method chaining
|
|
551
|
+
*/
|
|
552
|
+
registerEventHandler(t, e) {
|
|
553
|
+
return this._eventHandlers.set(t, e), this;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Apply domain event to aggregate and add to pending events
|
|
557
|
+
* @param {string | IDomainEvent<P>} eventTypeOrEvent - Event type string or complete event object
|
|
558
|
+
* @param {P} payload - Event payload data
|
|
559
|
+
* @param {Partial<IEventMetadata>} metadata - Optional event metadata
|
|
560
|
+
*/
|
|
561
|
+
apply(t, e, i) {
|
|
562
|
+
this._version++;
|
|
563
|
+
let r, a;
|
|
564
|
+
typeof t == "string" ? (r = N(t, e, i), a = r.metadata) : (r = t, a = i ? { ...r.metadata, ...i } : r.metadata);
|
|
565
|
+
const h = v.sanitizeMetadata({
|
|
566
|
+
...a,
|
|
567
|
+
aggregateId: this._id.toString(),
|
|
568
|
+
aggregateType: this.constructor.name,
|
|
569
|
+
aggregateVersion: this._version,
|
|
570
|
+
timestamp: a?.timestamp ?? /* @__PURE__ */ new Date()
|
|
571
|
+
}), g = Object.assign(
|
|
572
|
+
Object.create(Object.getPrototypeOf(r)),
|
|
573
|
+
r,
|
|
574
|
+
{ metadata: h }
|
|
575
|
+
);
|
|
576
|
+
if (this._maxEvents !== void 0 && this._domainEvents.length >= this._maxEvents)
|
|
577
|
+
throw new Error(
|
|
578
|
+
`Aggregate ${this.constructor.name} (id=${this._id.toString()}) exceeded maxEvents limit of ${this._maxEvents}. This usually indicates a runaway loop or replay of a corrupted event stream. Increase maxEvents in the aggregate constructor params if the limit is too restrictive for your domain.`
|
|
579
|
+
);
|
|
580
|
+
this._domainEvents.push(g), this.handleEvent(g);
|
|
581
|
+
}
|
|
582
|
+
handleEvent(t) {
|
|
583
|
+
const e = this.getCapability(f);
|
|
584
|
+
if (e)
|
|
585
|
+
e.handleVersionedEvent(t, this._eventHandlers);
|
|
586
|
+
else {
|
|
587
|
+
const i = this._eventHandlers.get(t.eventName);
|
|
588
|
+
i && i(t.payload, t.metadata);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Reconstruct aggregate state from historical domain events
|
|
593
|
+
* @param {IDomainEvent[]} events - Array of events to replay for state reconstruction
|
|
594
|
+
*/
|
|
595
|
+
loadFromHistory(t) {
|
|
596
|
+
this._version = this._initialVersion, this._domainEvents = [];
|
|
597
|
+
for (const e of t)
|
|
598
|
+
this.handleEvent(e), this._version++;
|
|
599
|
+
this._initialVersion = this._version;
|
|
600
|
+
}
|
|
601
|
+
// ==========================================
|
|
602
|
+
// TYPE-SAFE CAPABILITY MANAGEMENT
|
|
603
|
+
// ==========================================
|
|
604
|
+
/**
|
|
605
|
+
* Add capability to enhance aggregate functionality
|
|
606
|
+
* @param {T} capability - Capability instance to add to the aggregate
|
|
607
|
+
* @returns {this} The aggregate instance for method chaining
|
|
608
|
+
*/
|
|
609
|
+
addCapability(t) {
|
|
610
|
+
return "attach" in t && typeof t.attach == "function" && t.attach(this), this._capabilities.register(t), this;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* @param {CapabilityConstructor<T>} CapabilityClass - Constructor of the capability to retrieve
|
|
614
|
+
* @returns {T | undefined} The capability instance or undefined if not found
|
|
615
|
+
*/
|
|
616
|
+
getCapability(t) {
|
|
617
|
+
return this._capabilities.get(t);
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* @param {CapabilityConstructor<T>} CapabilityClass - Constructor of the capability to check
|
|
621
|
+
* @returns {boolean} True if the capability is present
|
|
622
|
+
*/
|
|
623
|
+
hasCapability(t) {
|
|
624
|
+
return this._capabilities.has(t);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* @param {CapabilityConstructor<T>} CapabilityClass - Constructor of the capability to remove
|
|
628
|
+
* @returns {this} The aggregate instance for method chaining
|
|
629
|
+
*/
|
|
630
|
+
removeCapability(t) {
|
|
631
|
+
const e = this._capabilities.get(t);
|
|
632
|
+
return e && "detach" in e && typeof e.detach == "function" && e.detach(), this._capabilities.remove(t), this;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* @returns {Capability[]} Array of all registered capabilities
|
|
636
|
+
*/
|
|
637
|
+
getAllCapabilities() {
|
|
638
|
+
return this._capabilities.getAll();
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* @returns {string[]} Array of capability type names
|
|
642
|
+
*/
|
|
643
|
+
getCapabilityTypes() {
|
|
644
|
+
return this._capabilities.getTypes();
|
|
645
|
+
}
|
|
646
|
+
// ==========================================
|
|
647
|
+
// SECURITY UTILITIES
|
|
648
|
+
// ==========================================
|
|
649
|
+
static sanitizeMetadata(t) {
|
|
650
|
+
if (!Object.prototype.hasOwnProperty.call(t, "__proto__") && !Object.prototype.hasOwnProperty.call(t, "constructor") && !Object.prototype.hasOwnProperty.call(t, "prototype"))
|
|
651
|
+
return t;
|
|
652
|
+
const e = {};
|
|
653
|
+
for (const i of Object.keys(t))
|
|
654
|
+
i === "__proto__" || i === "constructor" || i === "prototype" || (e[i] = t[i]);
|
|
655
|
+
return e;
|
|
656
|
+
}
|
|
657
|
+
// ==========================================
|
|
658
|
+
// INTERNAL ACCESS FOR CAPABILITIES
|
|
659
|
+
// ==========================================
|
|
660
|
+
/**
|
|
661
|
+
* Internal method for capabilities to access private state
|
|
662
|
+
* Should only be used by capabilities, not directly by domain code
|
|
663
|
+
*/
|
|
664
|
+
_internal_setState(t) {
|
|
665
|
+
this._version = t.version, this._initialVersion = t.initialVersion, this._domainEvents = [...t.domainEvents];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
class b extends p {
|
|
669
|
+
constructor() {
|
|
670
|
+
super(...arguments), this.type = "audit", this.auditLog = [];
|
|
671
|
+
}
|
|
672
|
+
static get capabilityType() {
|
|
673
|
+
return "audit";
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* @param {unknown} aggregate - Aggregate to attach this capability to
|
|
677
|
+
*/
|
|
678
|
+
attach(t) {
|
|
679
|
+
this.aggregate = t;
|
|
680
|
+
const e = this.aggregate;
|
|
681
|
+
typeof e.apply == "function" && (this.originalApply = e.apply, this.originalApply && (this.aggregate.apply = (...i) => {
|
|
682
|
+
const r = this.originalApply.call(this.aggregate, ...i);
|
|
683
|
+
return this.recordAuditEvent(), r;
|
|
684
|
+
}));
|
|
685
|
+
}
|
|
686
|
+
detach() {
|
|
687
|
+
this.aggregate && this.originalApply && (this.aggregate.apply = this.originalApply), this.aggregate = void 0, this.auditLog = [], this.originalApply = void 0;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* @returns {IAuditEvent[]} Copy of the current audit log
|
|
691
|
+
*/
|
|
692
|
+
getAuditLog() {
|
|
693
|
+
return [...this.auditLog];
|
|
694
|
+
}
|
|
695
|
+
clearAuditLog() {
|
|
696
|
+
this.auditLog = [];
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* @returns {{totalEvents: number, eventsByType: Record<string, number>, averageTimeBetweenEvents: number}} Audit statistics summary
|
|
700
|
+
*/
|
|
701
|
+
getAuditStatistics() {
|
|
702
|
+
const t = {};
|
|
703
|
+
let e = 0;
|
|
704
|
+
this.auditLog.forEach((r, a) => {
|
|
705
|
+
if (t[r.eventName] = (t[r.eventName] || 0) + 1, a > 0) {
|
|
706
|
+
const h = this.auditLog[a - 1];
|
|
707
|
+
if (h) {
|
|
708
|
+
const g = new Date(h.timestamp).getTime(), d = new Date(r.timestamp).getTime();
|
|
709
|
+
e += d - g;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
const i = this.auditLog.length > 1 ? e / (this.auditLog.length - 1) : 0;
|
|
714
|
+
return {
|
|
715
|
+
totalEvents: this.auditLog.length,
|
|
716
|
+
eventsByType: t,
|
|
717
|
+
averageTimeBetweenEvents: i
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Record an audit event when domain events are added
|
|
722
|
+
*/
|
|
723
|
+
recordAuditEvent() {
|
|
724
|
+
const t = this.aggregate.getDomainEvents(), e = t[t.length - 1];
|
|
725
|
+
e && this.recordEvent(e);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* @param {IDomainEvent} event - Domain event to record in audit log
|
|
729
|
+
*/
|
|
730
|
+
recordEvent(t) {
|
|
731
|
+
const e = {
|
|
732
|
+
eventId: t.metadata?.eventId || `audit-${Date.now()}-${Math.random()}`,
|
|
733
|
+
eventName: t.eventName,
|
|
734
|
+
aggregateId: this.aggregate.getId().toString(),
|
|
735
|
+
aggregateType: this.aggregate.constructor.name,
|
|
736
|
+
aggregateVersion: this.aggregate.getVersion(),
|
|
737
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
738
|
+
payload: t.payload,
|
|
739
|
+
metadata: {
|
|
740
|
+
...t.metadata,
|
|
741
|
+
auditCapability: !0
|
|
742
|
+
},
|
|
743
|
+
actor: t.metadata?.userId
|
|
744
|
+
};
|
|
745
|
+
this.auditLog.push(e);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* @param {string} eventName - Name of events to filter by
|
|
749
|
+
* @returns {IAuditEvent[]} Array of audit events matching the specified name
|
|
750
|
+
*/
|
|
751
|
+
getEventsByName(t) {
|
|
752
|
+
return this.auditLog.filter((e) => e.eventName === t);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* @param {Date} startDate - Start date for time range filter
|
|
756
|
+
* @param {Date} endDate - End date for time range filter
|
|
757
|
+
* @returns {IAuditEvent[]} Array of audit events within the specified time range
|
|
758
|
+
*/
|
|
759
|
+
getEventsByTimeRange(t, e) {
|
|
760
|
+
return this.auditLog.filter((i) => {
|
|
761
|
+
const r = new Date(i.timestamp).getTime();
|
|
762
|
+
return r >= t.getTime() && r <= e.getTime();
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* @returns {IAuditEvent | null} First audit event in chronological order or null if none exists
|
|
767
|
+
*/
|
|
768
|
+
getFirstEvent() {
|
|
769
|
+
return this.auditLog[0] || null;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* @returns {IAuditEvent | null} Last audit event in chronological order or null if none exists
|
|
773
|
+
*/
|
|
774
|
+
getLastEvent() {
|
|
775
|
+
return this.auditLog[this.auditLog.length - 1] || null;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
class n extends I {
|
|
779
|
+
/**
|
|
780
|
+
* Error for invalid arguments provided to the aggregate
|
|
781
|
+
*/
|
|
782
|
+
static invalidArguments(t, e) {
|
|
783
|
+
const i = {
|
|
784
|
+
code: c.InvalidParameter,
|
|
785
|
+
data: e
|
|
786
|
+
};
|
|
787
|
+
return new n(t, i);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Error for version conflicts in the aggregate
|
|
791
|
+
*/
|
|
792
|
+
static versionConflict(t, e, i, r) {
|
|
793
|
+
const a = `Version conflict: Aggregate ${t} with ID ${e} has version ${i}, but expected ${r}`, h = {
|
|
794
|
+
code: c.ValidationFailed,
|
|
795
|
+
data: {
|
|
796
|
+
aggregateType: t,
|
|
797
|
+
aggregateId: e,
|
|
798
|
+
currentVersion: i,
|
|
799
|
+
expectedVersion: r
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
return new n(a, h);
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Error for when a required feature is not enabled
|
|
806
|
+
*/
|
|
807
|
+
static featureNotEnabled(t) {
|
|
808
|
+
const e = `Feature '${t}' is not enabled on aggregate`, i = {
|
|
809
|
+
code: c.ValidationFailed,
|
|
810
|
+
data: {
|
|
811
|
+
feature: t
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
return new n(e, i);
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Error for when a required method is not implemented
|
|
818
|
+
*/
|
|
819
|
+
static methodNotImplemented(t, e) {
|
|
820
|
+
const i = `Method '${t}' must be implemented by ${e} to use this feature`, r = {
|
|
821
|
+
code: c.MissingValue,
|
|
822
|
+
data: {
|
|
823
|
+
methodName: t,
|
|
824
|
+
aggregateType: e
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
return new n(i, r);
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Error for invalid snapshot
|
|
831
|
+
*/
|
|
832
|
+
static invalidSnapshot(t, e) {
|
|
833
|
+
const i = `Invalid snapshot for aggregate ${t}${e ? `: ${e}` : ""}`, r = {
|
|
834
|
+
code: c.InvalidFormat,
|
|
835
|
+
data: {
|
|
836
|
+
aggregateType: t,
|
|
837
|
+
reason: e
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
return new n(i, r);
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Error for ID mismatch during snapshot restoration
|
|
844
|
+
*/
|
|
845
|
+
static idMismatch(t, e) {
|
|
846
|
+
const i = `ID mismatch: Snapshot is for ID ${t}, but aggregate has ID ${e}`, r = {
|
|
847
|
+
code: c.ValidationFailed,
|
|
848
|
+
data: {
|
|
849
|
+
snapshotId: t,
|
|
850
|
+
aggregateId: e
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
return new n(i, r);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Error for type mismatch during snapshot restoration
|
|
857
|
+
*/
|
|
858
|
+
static typeMismatch(t, e) {
|
|
859
|
+
const i = `Aggregate type mismatch: Snapshot is for type ${t}, but trying to load into ${e}`, r = {
|
|
860
|
+
code: c.ValidationFailed,
|
|
861
|
+
data: {
|
|
862
|
+
snapshotType: t,
|
|
863
|
+
aggregateType: e
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
return new n(i, r);
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Error for duplicate upcaster registration
|
|
870
|
+
*/
|
|
871
|
+
static duplicateUpcaster(t, e) {
|
|
872
|
+
const i = `Upcaster for event ${t} version ${e} already exists`, r = {
|
|
873
|
+
code: c.DuplicateEntry,
|
|
874
|
+
data: {
|
|
875
|
+
eventType: t,
|
|
876
|
+
sourceVersion: e
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
return new n(i, r);
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Error for missing upcaster
|
|
883
|
+
*/
|
|
884
|
+
static missingUpcaster(t, e, i) {
|
|
885
|
+
const r = `Missing upcaster for event ${t} from version ${e} to ${i}`, a = {
|
|
886
|
+
code: c.MissingValue,
|
|
887
|
+
data: {
|
|
888
|
+
eventType: t,
|
|
889
|
+
fromVersion: e,
|
|
890
|
+
toVersion: i
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
return new n(r, a);
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Error for configuration problems
|
|
897
|
+
*/
|
|
898
|
+
static configurationError(t, e) {
|
|
899
|
+
const i = `Configuration error in ${t}: ${e}`, r = {
|
|
900
|
+
code: c.ValidationFailed,
|
|
901
|
+
data: {
|
|
902
|
+
aggregateType: t,
|
|
903
|
+
configurationError: e
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
return new n(i, r);
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Error for when apply method interception fails (e.g. method not found)
|
|
910
|
+
*/
|
|
911
|
+
static cannotInterceptApplyMethod(t, e) {
|
|
912
|
+
const i = `Cannot intercept apply method - method not found on aggregate ${t}`, r = {
|
|
913
|
+
code: c.InternalError,
|
|
914
|
+
data: {
|
|
915
|
+
aggergateId: t,
|
|
916
|
+
...e
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
return new n(i, r);
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Error for when event store is not configured
|
|
923
|
+
*/
|
|
924
|
+
static eventStoreNotConfigured(t) {
|
|
925
|
+
const e = "Event store not configured", i = {
|
|
926
|
+
code: c.MissingValue,
|
|
927
|
+
data: t
|
|
928
|
+
};
|
|
929
|
+
return new n(e, i);
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Error for when aggregate does not support event replay
|
|
933
|
+
*/
|
|
934
|
+
static aggregateDoesNotSupportReplay(t, e) {
|
|
935
|
+
const i = `Aggregate ${t} does not support event replay`, r = {
|
|
936
|
+
code: c.MethodNotSupported,
|
|
937
|
+
data: {
|
|
938
|
+
aggregateType: t,
|
|
939
|
+
...e
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
return new n(i, r);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
class w extends p {
|
|
946
|
+
constructor() {
|
|
947
|
+
super(...arguments), this.type = "eventSourcing", this.eventStore = null;
|
|
948
|
+
}
|
|
949
|
+
static get capabilityType() {
|
|
950
|
+
return "eventSourcing";
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* @param {unknown} aggregate - Aggregate to attach this capability to
|
|
954
|
+
*/
|
|
955
|
+
attach(t) {
|
|
956
|
+
this.aggregate = t;
|
|
957
|
+
}
|
|
958
|
+
detach() {
|
|
959
|
+
this.aggregate = void 0, this.eventStore = null;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* @param {IEventStore} eventStore - Event store instance to use for persistence
|
|
963
|
+
*/
|
|
964
|
+
setEventStore(t) {
|
|
965
|
+
this.eventStore = t;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* @returns {IEventStore | null} Currently configured event store or null
|
|
969
|
+
*/
|
|
970
|
+
getEventStore() {
|
|
971
|
+
return this.eventStore;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* @param {string | number} aggregateId - ID of aggregate to load events for
|
|
975
|
+
* @throws {AggregateError} When event store is not configured
|
|
976
|
+
*/
|
|
977
|
+
async loadFromEventStore(t) {
|
|
978
|
+
if (!this.eventStore)
|
|
979
|
+
throw n.configurationError(
|
|
980
|
+
this.aggregate.constructor.name,
|
|
981
|
+
"Event store not configured"
|
|
982
|
+
);
|
|
983
|
+
const e = await this.eventStore.getEvents(t);
|
|
984
|
+
if (e.length === 0)
|
|
985
|
+
return;
|
|
986
|
+
const i = this.aggregate;
|
|
987
|
+
i.loadFromHistory && i.loadFromHistory(e);
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* @throws {AggregateError} When event store is not configured
|
|
991
|
+
*/
|
|
992
|
+
async saveToEventStore() {
|
|
993
|
+
if (!this.eventStore)
|
|
994
|
+
throw n.configurationError(
|
|
995
|
+
this.aggregate.constructor.name,
|
|
996
|
+
"Event store not configured"
|
|
997
|
+
);
|
|
998
|
+
const t = this.aggregate.getDomainEvents();
|
|
999
|
+
t.length !== 0 && await this.eventStore.saveEvents(
|
|
1000
|
+
this.aggregate.getId().toString(),
|
|
1001
|
+
[...t],
|
|
1002
|
+
this.aggregate.getInitialVersion()
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* @returns {boolean} True if event store is configured
|
|
1007
|
+
*/
|
|
1008
|
+
hasEventStore() {
|
|
1009
|
+
return this.eventStore !== null;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* @returns {string} Stream name combining aggregate type and ID
|
|
1013
|
+
*/
|
|
1014
|
+
getStreamName() {
|
|
1015
|
+
return `${this.aggregate.constructor.name}-${this.aggregate.getId().toString()}`;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* @param {string | number} aggregateId - ID of aggregate to load events for
|
|
1019
|
+
* @param {number} fromVersion - Version number to start loading from
|
|
1020
|
+
* @throws {AggregateError} When event store is not configured
|
|
1021
|
+
*/
|
|
1022
|
+
async loadFromVersion(t, e) {
|
|
1023
|
+
if (!this.eventStore)
|
|
1024
|
+
throw n.configurationError(
|
|
1025
|
+
this.aggregate.constructor.name,
|
|
1026
|
+
"Event store not configured"
|
|
1027
|
+
);
|
|
1028
|
+
const i = await this.eventStore.getEventsAfterVersion(t, e), r = this.aggregate;
|
|
1029
|
+
r.loadFromHistory && i.length > 0 && r.loadFromHistory(i);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
class S extends p {
|
|
1033
|
+
constructor() {
|
|
1034
|
+
super(...arguments), this.type = "snapshot", this._snapshot = null;
|
|
1035
|
+
}
|
|
1036
|
+
static get capabilityType() {
|
|
1037
|
+
return "snapshot";
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* @param {unknown} aggregate - Aggregate to attach this capability to
|
|
1041
|
+
*/
|
|
1042
|
+
attach(t) {
|
|
1043
|
+
this.aggregate = t;
|
|
1044
|
+
}
|
|
1045
|
+
detach() {
|
|
1046
|
+
this.aggregate = void 0, this._snapshot = null;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* @param {() => TState} serializer - Function to serialize aggregate state
|
|
1050
|
+
* @param {() => TMeta} metadataCreator - Optional function to create snapshot metadata
|
|
1051
|
+
* @returns {IAggregateSnapshot<TState, TMeta>} Created snapshot with state and metadata
|
|
1052
|
+
*/
|
|
1053
|
+
createSnapshot(t, e) {
|
|
1054
|
+
const i = this.aggregate.getDomainEvents(), a = (i.length > 0 ? i[i.length - 1] : null)?.metadata?.eventId, g = {
|
|
1055
|
+
aggregateId: this.aggregate.getId().toString(),
|
|
1056
|
+
version: this.aggregate.getVersion(),
|
|
1057
|
+
aggregateType: this.aggregate.constructor.name,
|
|
1058
|
+
state: t(),
|
|
1059
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1060
|
+
...a !== void 0 && { lastEventId: a }
|
|
1061
|
+
};
|
|
1062
|
+
return e && (g.metadata = e()), g;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* @param {IAggregateSnapshot<TState, TMeta>} snapshot - Snapshot to restore from
|
|
1066
|
+
* @param {(state: TState) => void} deserializer - Function to deserialize state into aggregate
|
|
1067
|
+
* @param {(metadata: TMeta) => void} metadataRestorer - Optional function to restore metadata
|
|
1068
|
+
* @throws {AggregateError} When snapshot is invalid or IDs don't match
|
|
1069
|
+
*/
|
|
1070
|
+
restoreFromSnapshot(t, e, i) {
|
|
1071
|
+
if (!t || !t.state)
|
|
1072
|
+
throw n.invalidSnapshot(this.aggregate.constructor.name, "Invalid snapshot");
|
|
1073
|
+
const r = this.aggregate.getId().toString();
|
|
1074
|
+
if (t.aggregateId !== r)
|
|
1075
|
+
throw n.idMismatch(t.aggregateId, r);
|
|
1076
|
+
if (t.aggregateType !== this.aggregate.constructor.name)
|
|
1077
|
+
throw n.typeMismatch(t.aggregateType, this.aggregate.constructor.name);
|
|
1078
|
+
e(t.state), t.metadata && i && i(t.metadata);
|
|
1079
|
+
const a = this.aggregate;
|
|
1080
|
+
a._internal_setState && a._internal_setState({
|
|
1081
|
+
version: t.version,
|
|
1082
|
+
initialVersion: t.version,
|
|
1083
|
+
domainEvents: []
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* @param {TState} state - State to save temporarily
|
|
1088
|
+
*/
|
|
1089
|
+
saveTemporaryState(t) {
|
|
1090
|
+
this._snapshot = this.createSnapshot(() => t);
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* @returns {Date | null} Timestamp of last snapshot or null if none exists
|
|
1094
|
+
*/
|
|
1095
|
+
getLastSnapshotTimestamp() {
|
|
1096
|
+
return this._snapshot?.timestamp || null;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* @param {() => TState} serializer - Function to serialize aggregate state
|
|
1100
|
+
* @param {() => TMeta} metadataCreator - Optional metadata creator function
|
|
1101
|
+
*/
|
|
1102
|
+
saveSnapshot(t, e) {
|
|
1103
|
+
this._snapshot = this.createSnapshot(t, e);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* @returns {IAggregateSnapshot<TState, TMeta> | null} Previous snapshot state if exists
|
|
1107
|
+
*/
|
|
1108
|
+
getPreviousState() {
|
|
1109
|
+
const t = this._snapshot;
|
|
1110
|
+
return this._snapshot = null, t;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
class m {
|
|
1114
|
+
constructor(t) {
|
|
1115
|
+
this.capabilities = [], this.params = t;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* @returns Aggregate
|
|
1119
|
+
*/
|
|
1120
|
+
static create(t) {
|
|
1121
|
+
const e = {
|
|
1122
|
+
id: t.id instanceof u ? t.id : new u(t.id, "text"),
|
|
1123
|
+
version: t.version || 0
|
|
1124
|
+
};
|
|
1125
|
+
return new m(e);
|
|
1126
|
+
}
|
|
1127
|
+
withSnapshots() {
|
|
1128
|
+
return this.capabilities.push({ capability: new S() }), this;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* @returns {this} Builder instance for method chaining
|
|
1132
|
+
*/
|
|
1133
|
+
withVersioning() {
|
|
1134
|
+
return this.capabilities.push({ capability: new f() }), this;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* @returns {this} Builder instance for method chaining
|
|
1138
|
+
*/
|
|
1139
|
+
withAudit() {
|
|
1140
|
+
return this.capabilities.push({ capability: new b() }), this;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* @param {IEventStore} [eventStore] - Optional event store for event sourcing
|
|
1144
|
+
* @returns {this} Builder instance for method chaining
|
|
1145
|
+
*/
|
|
1146
|
+
withEventSourcing(t) {
|
|
1147
|
+
const e = new w();
|
|
1148
|
+
return this.capabilities.push({
|
|
1149
|
+
capability: e,
|
|
1150
|
+
configure: (i) => {
|
|
1151
|
+
const r = i;
|
|
1152
|
+
t ? r.setEventStore(t) : this.eventStore && r.setEventStore(this.eventStore);
|
|
1153
|
+
}
|
|
1154
|
+
}), this;
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* @param {T} capability - Custom capability instance to add
|
|
1158
|
+
* @param {(cap: T) => void} configure - Optional configuration function for the capability
|
|
1159
|
+
* @returns {this} Builder instance for method chaining
|
|
1160
|
+
*/
|
|
1161
|
+
withCapability(t, e) {
|
|
1162
|
+
return this.capabilities.push({
|
|
1163
|
+
capability: t,
|
|
1164
|
+
configure: e ? (i) => e(i) : void 0
|
|
1165
|
+
}), this;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* @param {IEventStore} eventStore - Event store instance for event sourcing capability
|
|
1169
|
+
* @returns {this} Builder instance for method chaining
|
|
1170
|
+
*/
|
|
1171
|
+
setEventStore(t) {
|
|
1172
|
+
return this.eventStore = t, this;
|
|
1173
|
+
}
|
|
1174
|
+
build(t) {
|
|
1175
|
+
const e = t || v, i = new e(this.params);
|
|
1176
|
+
for (const { capability: r, configure: a } of this.capabilities)
|
|
1177
|
+
i.addCapability(r), a && a(r);
|
|
1178
|
+
return i;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* @param {new (params: IAggregateConstructorParams<TId>) => TAgg} AggregateClass - Optional aggregate class constructor
|
|
1182
|
+
* @returns {TAgg} Fully configured aggregate with all standard capabilities
|
|
1183
|
+
*/
|
|
1184
|
+
buildWithAllCapabilities(t) {
|
|
1185
|
+
return this.withSnapshots().withVersioning().withAudit().withEventSourcing().build(t);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
function L(s) {
|
|
1189
|
+
return m.create(s);
|
|
1190
|
+
}
|
|
1191
|
+
class E {
|
|
1192
|
+
constructor(t) {
|
|
1193
|
+
this._id = t;
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Return this entity's unique identifier.
|
|
1197
|
+
*/
|
|
1198
|
+
getId() {
|
|
1199
|
+
return this._id;
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Identity-based equality. Two entities are equal iff they have the same
|
|
1203
|
+
* id (compared via the EntityId's own equality). Attribute differences
|
|
1204
|
+
* are ignored.
|
|
1205
|
+
*
|
|
1206
|
+
* @param other - Entity to compare against. May be `null` or `undefined`.
|
|
1207
|
+
* @returns `true` if both entities have the same id; `false` otherwise.
|
|
1208
|
+
*/
|
|
1209
|
+
equals(t) {
|
|
1210
|
+
return t == null ? !1 : t === this ? !0 : t instanceof E ? this._id.equals(t._id) : !1;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
function x(s) {
|
|
1214
|
+
return s.hasCapability(S);
|
|
1215
|
+
}
|
|
1216
|
+
function C(s) {
|
|
1217
|
+
return s.hasCapability(f);
|
|
1218
|
+
}
|
|
1219
|
+
function V(s) {
|
|
1220
|
+
return s.hasCapability(b);
|
|
1221
|
+
}
|
|
1222
|
+
function A(s) {
|
|
1223
|
+
return s.hasCapability(w);
|
|
1224
|
+
}
|
|
1225
|
+
function U(s) {
|
|
1226
|
+
if (!x(s))
|
|
1227
|
+
throw n.featureNotEnabled("snapshot");
|
|
1228
|
+
return s;
|
|
1229
|
+
}
|
|
1230
|
+
function k(s) {
|
|
1231
|
+
return x(s) ? o.ok(s) : o.fail(n.featureNotEnabled("snapshot"));
|
|
1232
|
+
}
|
|
1233
|
+
function O(s) {
|
|
1234
|
+
if (!C(s))
|
|
1235
|
+
throw n.featureNotEnabled("versioning");
|
|
1236
|
+
return s;
|
|
1237
|
+
}
|
|
1238
|
+
function B(s) {
|
|
1239
|
+
return C(s) ? o.ok(s) : o.fail(n.featureNotEnabled("versioning"));
|
|
1240
|
+
}
|
|
1241
|
+
function H(s) {
|
|
1242
|
+
if (!V(s))
|
|
1243
|
+
throw n.featureNotEnabled("audit");
|
|
1244
|
+
return s;
|
|
1245
|
+
}
|
|
1246
|
+
function P(s) {
|
|
1247
|
+
return V(s) ? o.ok(s) : o.fail(n.featureNotEnabled("audit"));
|
|
1248
|
+
}
|
|
1249
|
+
function j(s) {
|
|
1250
|
+
if (!A(s))
|
|
1251
|
+
throw n.featureNotEnabled("eventSourcing");
|
|
1252
|
+
return s;
|
|
1253
|
+
}
|
|
1254
|
+
function z(s) {
|
|
1255
|
+
return A(s) ? o.ok(s) : o.fail(n.featureNotEnabled("eventSourcing"));
|
|
1256
|
+
}
|
|
1257
|
+
function W(s) {
|
|
1258
|
+
return s.getCapabilityTypes();
|
|
1259
|
+
}
|
|
1260
|
+
function q(s, t) {
|
|
1261
|
+
return t.every((e) => s.hasCapability(e));
|
|
1262
|
+
}
|
|
1263
|
+
export {
|
|
1264
|
+
m as AggregateBuilder,
|
|
1265
|
+
n as AggregateError,
|
|
1266
|
+
v as AggregateRoot,
|
|
1267
|
+
b as AuditCapability,
|
|
1268
|
+
E as Entity,
|
|
1269
|
+
w as EventSourcingCapability,
|
|
1270
|
+
S as SnapshotCapability,
|
|
1271
|
+
f as VersioningCapability,
|
|
1272
|
+
L as aggregateBuilder,
|
|
1273
|
+
H as asAuditAggregate,
|
|
1274
|
+
j as asEventSourcingAggregate,
|
|
1275
|
+
U as asSnapshotAggregate,
|
|
1276
|
+
O as asVersioningAggregate,
|
|
1277
|
+
W as getAggregateCapabilities,
|
|
1278
|
+
q as hasAllCapabilities,
|
|
1279
|
+
P as tryAsAuditAggregate,
|
|
1280
|
+
z as tryAsEventSourcingAggregate,
|
|
1281
|
+
k as tryAsSnapshotAggregate,
|
|
1282
|
+
B as tryAsVersioningAggregate
|
|
1283
|
+
};
|