@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/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
+ };