@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.
@@ -0,0 +1,233 @@
1
+ import { Result } from '../../../utils/src/index.ts';
2
+ import { CapabilityConstructor, Capability, IAggregateCapability, IAggregateSnapshot } from '../../../contracts/src/index.ts';
3
+ import { AggregateError } from '../aggregate-errors';
4
+ import { SnapshotCapability } from '../capabilities/snapshot-capability';
5
+ import { VersioningCapability } from '../capabilities/versioning-capability';
6
+ import { EventSourcingCapability } from '../capabilities/event-sourcing-capability';
7
+ import { AuditCapability } from '../capabilities/audit-capability';
8
+ import { AggregateRoot } from './aggregate-root';
9
+ /**
10
+ * Type utility for aggregates with specific capability requirements.
11
+ * Provides compile-time type safety for capability-dependent operations.
12
+ */
13
+ export type AggregateWithCapability<TId, TCap extends Capability> = AggregateRoot<TId> & {
14
+ getCapability<T extends TCap>(CapabilityClass: CapabilityConstructor<T>): T;
15
+ };
16
+ /**
17
+ * Type alias for aggregates with snapshot capability.
18
+ * Ensures aggregate has snapshot functionality for state persistence.
19
+ */
20
+ export type AggregateWithSnapshotCapability<TId> = AggregateWithCapability<TId, SnapshotCapability>;
21
+ /**
22
+ * Type alias for aggregates with versioning capability.
23
+ * Ensures aggregate has event versioning and upcasting functionality.
24
+ */
25
+ export type AggregateWithVersioningCapability<TId> = AggregateWithCapability<TId, VersioningCapability>;
26
+ /**
27
+ * Type alias for aggregates with audit capability.
28
+ * Ensures aggregate has audit logging functionality for compliance.
29
+ */
30
+ export type AggregateWithAuditCapability<TId> = AggregateWithCapability<TId, AuditCapability>;
31
+ /**
32
+ * Type alias for aggregates with event sourcing capability.
33
+ * Ensures aggregate has event store integration for persistence.
34
+ */
35
+ export type AggregateWithEventSourcingCapability<TId> = AggregateWithCapability<TId, EventSourcingCapability>;
36
+ /**
37
+ * @param {AggregateRoot<TId>} aggregate - aggregate parameter
38
+ * @returns {aggregate is AggregateWithSnapshotCapability<TId>} Returns aggregate is AggregateWithSnapshotCapability<TId>
39
+ * @throws {Error} When validation fails
40
+ */
41
+ export declare function hasSnapshotCapability<TId>(aggregate: AggregateRoot<TId>): aggregate is AggregateWithSnapshotCapability<TId>;
42
+ /**
43
+ * @param {AggregateRoot<TId>} aggregate - aggregate parameter
44
+ * @returns {aggregate is AggregateWithVersioningCapability<TId>} Returns aggregate is AggregateWithVersioningCapability<TId>
45
+ * @throws {Error} When validation fails
46
+ */
47
+ export declare function hasVersioningCapability<TId>(aggregate: AggregateRoot<TId>): aggregate is AggregateWithVersioningCapability<TId>;
48
+ /**
49
+ * @param {AggregateRoot<TId>} aggregate - aggregate parameter
50
+ * @returns {aggregate is AggregateWithAuditCapability<TId>} Returns aggregate is AggregateWithAuditCapability<TId>
51
+ * @throws {Error} When validation fails
52
+ */
53
+ export declare function hasAuditCapability<TId>(aggregate: AggregateRoot<TId>): aggregate is AggregateWithAuditCapability<TId>;
54
+ /**
55
+ * @param {AggregateRoot<TId>} aggregate - aggregate parameter
56
+ * @returns {aggregate is AggregateWithEventSourcingCapability<TId>} Returns aggregate is AggregateWithEventSourcingCapability<TId>
57
+ * @throws {Error} When validation fails
58
+ */
59
+ export declare function hasEventSourcingCapability<TId>(aggregate: AggregateRoot<TId>): aggregate is AggregateWithEventSourcingCapability<TId>;
60
+ /**
61
+ * @param {AggregateRoot<TId>} aggregate - aggregate parameter
62
+ * @returns {AggregateWithSnapshotCapability<TId>} Returns AggregateWithSnapshotCapability<TId>
63
+ * @throws {Error} When validation fails
64
+ */
65
+ export declare function asSnapshotAggregate<TId>(aggregate: AggregateRoot<TId>): AggregateWithSnapshotCapability<TId>;
66
+ /**
67
+ * Returns the aggregate cast to snapshot-capable type, or a failed Result if the capability
68
+ * is not registered. Use this variant when missing capability is an expected condition.
69
+ *
70
+ * @param aggregate - Aggregate to cast
71
+ * @returns `Result.ok` with the cast aggregate, or `Result.fail` with an {@link AggregateError}
72
+ * @public
73
+ * @stable
74
+ * @since 0.24.0
75
+ */
76
+ export declare function tryAsSnapshotAggregate<TId>(aggregate: AggregateRoot<TId>): Result<AggregateWithSnapshotCapability<TId>, AggregateError>;
77
+ /**
78
+ * @param {AggregateRoot<TId>} aggregate - aggregate parameter
79
+ * @returns {AggregateWithVersioningCapability<TId>} Returns AggregateWithVersioningCapability<TId>
80
+ * @throws {Error} When validation fails
81
+ */
82
+ export declare function asVersioningAggregate<TId>(aggregate: AggregateRoot<TId>): AggregateWithVersioningCapability<TId>;
83
+ /**
84
+ * Returns the aggregate cast to versioning-capable type, or a failed Result if the capability
85
+ * is not registered. Use this variant when missing capability is an expected condition.
86
+ *
87
+ * @param aggregate - Aggregate to cast
88
+ * @returns `Result.ok` with the cast aggregate, or `Result.fail` with an {@link AggregateError}
89
+ * @public
90
+ * @stable
91
+ * @since 0.24.0
92
+ */
93
+ export declare function tryAsVersioningAggregate<TId>(aggregate: AggregateRoot<TId>): Result<AggregateWithVersioningCapability<TId>, AggregateError>;
94
+ /**
95
+ * @param {AggregateRoot<TId>} aggregate - aggregate parameter
96
+ * @returns {AggregateWithAuditCapability<TId>} Returns AggregateWithAuditCapability<TId>
97
+ * @throws {Error} When validation fails
98
+ */
99
+ export declare function asAuditAggregate<TId>(aggregate: AggregateRoot<TId>): AggregateWithAuditCapability<TId>;
100
+ /**
101
+ * Returns the aggregate cast to audit-capable type, or a failed Result if the capability
102
+ * is not registered. Use this variant when missing capability is an expected condition.
103
+ *
104
+ * @param aggregate - Aggregate to cast
105
+ * @returns `Result.ok` with the cast aggregate, or `Result.fail` with an {@link AggregateError}
106
+ * @public
107
+ * @stable
108
+ * @since 0.24.0
109
+ */
110
+ export declare function tryAsAuditAggregate<TId>(aggregate: AggregateRoot<TId>): Result<AggregateWithAuditCapability<TId>, AggregateError>;
111
+ /**
112
+ * @param {AggregateRoot<TId>} aggregate - aggregate parameter
113
+ * @returns {AggregateWithEventSourcingCapability<TId>} Returns AggregateWithEventSourcingCapability<TId>
114
+ * @throws {Error} When validation fails
115
+ */
116
+ export declare function asEventSourcingAggregate<TId>(aggregate: AggregateRoot<TId>): AggregateWithEventSourcingCapability<TId>;
117
+ /**
118
+ * Returns the aggregate cast to event-sourcing-capable type, or a failed Result if the
119
+ * capability is not registered. Use this variant when missing capability is an expected condition.
120
+ *
121
+ * @param aggregate - Aggregate to cast
122
+ * @returns `Result.ok` with the cast aggregate, or `Result.fail` with an {@link AggregateError}
123
+ * @public
124
+ * @stable
125
+ * @since 0.24.0
126
+ */
127
+ export declare function tryAsEventSourcingAggregate<TId>(aggregate: AggregateRoot<TId>): Result<AggregateWithEventSourcingCapability<TId>, AggregateError>;
128
+ /**
129
+ * @param {AggregateRoot<unknown>} aggregate - aggregate parameter
130
+ * @returns {string[]} Returns string[]
131
+ * @throws {Error} When validation fails
132
+ */
133
+ export declare function getAggregateCapabilities(aggregate: AggregateRoot<unknown>): string[];
134
+ /**
135
+ * @param {AggregateRoot<unknown>} aggregate - aggregate parameter
136
+ * @param {CapabilityConstructor<T>[]} capabilities - capabilities parameter
137
+ * @returns {boolean} Returns boolean
138
+ * @throws {Error} When validation fails
139
+ */
140
+ export declare function hasAllCapabilities<T extends Capability & IAggregateCapability>(aggregate: AggregateRoot<unknown>, capabilities: CapabilityConstructor<T>[]): boolean;
141
+ /**
142
+ * @param {AggregateRoot<unknown>} aggregate - aggregate parameter
143
+ * @param {CapabilityConstructor<T>[]} capabilities - capabilities parameter
144
+ * @returns {boolean} Returns boolean
145
+ * @throws {Error} When validation fails
146
+ */
147
+ export declare function hasAnyCapability<T extends Capability & IAggregateCapability>(aggregate: AggregateRoot<unknown>, capabilities: CapabilityConstructor<T>[]): boolean;
148
+ /**
149
+ * @param {AggregateRoot<unknown>} aggregate - aggregate parameter
150
+ * @throws {Error} When validation fails
151
+ */
152
+ export declare function getAggregateInfo(aggregate: AggregateRoot<unknown>): {
153
+ id: unknown;
154
+ type: string;
155
+ version: number;
156
+ hasChanges: boolean;
157
+ capabilities: string[];
158
+ events: number;
159
+ };
160
+ /**
161
+ * @param {AggregateRoot<unknown>} aggregate - Aggregate to create snapshot for
162
+ * @param {() => TState} serializer - Function to serialize aggregate state
163
+ * @param {() => unknown} metadataCreator - Optional metadata creator function
164
+ * @returns {unknown | null} Snapshot object if capability exists, null otherwise
165
+ */
166
+ export declare function createSnapshotIfCapable<TState>(aggregate: AggregateRoot<unknown>, serializer: () => TState, metadataCreator?: () => unknown): unknown | null;
167
+ /**
168
+ * @param {AggregateRoot<unknown>} aggregate - aggregate parameter
169
+ * @param {IAggregateSnapshot<unknown} snapshot - snapshot parameter
170
+ * @param {(state: TState} deserializer - deserializer parameter
171
+ * @returns {boolean} Returns boolean
172
+ * @throws {Error} When validation fails
173
+ */
174
+ export declare function restoreFromSnapshotIfCapable<TState = unknown>(aggregate: AggregateRoot<unknown>, snapshot: IAggregateSnapshot<unknown, unknown>, deserializer: (state: TState) => void, metadataRestorer?: (metadata: unknown) => void): boolean;
175
+ /**
176
+ * @param {AggregateRoot<unknown>} aggregate - aggregate parameter
177
+ * @returns {unknown[]} Returns unknown[]
178
+ * @throws {Error} When validation fails
179
+ */
180
+ export declare function getAuditLogIfCapable(aggregate: AggregateRoot<unknown>): unknown[];
181
+ /**
182
+ * @param {AggregateRoot<unknown>} aggregate - Aggregate to get audit statistics from
183
+ * @returns {unknown | null} Audit statistics if capability exists, null otherwise
184
+ */
185
+ export declare function getAuditStatsIfCapable(aggregate: AggregateRoot<unknown>): unknown | null;
186
+ /**
187
+ * @param {AggregateRoot<unknown>} aggregate - aggregate parameter
188
+ * @param {string | number} aggregateId - aggregateId parameter
189
+ * @returns {Promise<boolean>} Returns Promise<boolean>
190
+ * @throws {Error} When validation fails
191
+ */
192
+ export declare function loadFromEventStoreIfCapable(aggregate: AggregateRoot<unknown>, aggregateId: string | number): Promise<boolean>;
193
+ /**
194
+ * @param {AggregateRoot<unknown>} aggregate - aggregate parameter
195
+ * @returns {Promise<boolean>} Returns Promise<boolean>
196
+ * @throws {Error} When validation fails
197
+ */
198
+ export declare function saveToEventStoreIfCapable(aggregate: AggregateRoot<unknown>): Promise<boolean>;
199
+ /**
200
+ * @param {AggregateRoot<unknown>} aggregate - aggregate parameter
201
+ * @param {string} eventType - eventType parameter
202
+ * @param {number} sourceVersion - sourceVersion parameter
203
+ * @param {{ upcast(payload: TFrom} upcaster - upcaster parameter
204
+ * @param {unknown} metadata? - metadata? parameter
205
+ * @returns {TTo }} Returns TTo }
206
+ * @throws {Error} When validation fails
207
+ */
208
+ export declare function registerUpcasterIfCapable<TFrom, TTo>(aggregate: AggregateRoot<unknown>, eventType: string, sourceVersion: number, upcaster: {
209
+ upcast(payload: TFrom, metadata?: unknown): TTo;
210
+ }): boolean;
211
+ /**
212
+ * @param {AggregateRoot<unknown>} aggregate - Aggregate to get versioning info from
213
+ * @returns {unknown | null} Versioning information if capability exists, null otherwise
214
+ */
215
+ export declare function getVersioningInfoIfCapable(aggregate: AggregateRoot<unknown>): unknown | null;
216
+ /**
217
+ * @throws {Error} When validation fails
218
+ */
219
+ export declare function processAggregatesWithCapabilities<TId>(aggregates: AggregateRoot<TId>[], processors: {
220
+ snapshot?: (aggregate: AggregateWithSnapshotCapability<TId>) => void;
221
+ audit?: (aggregate: AggregateWithAuditCapability<TId>) => void;
222
+ versioning?: (aggregate: AggregateWithVersioningCapability<TId>) => void;
223
+ eventSourcing?: (aggregate: AggregateWithEventSourcingCapability<TId>) => Promise<void>;
224
+ }): Promise<void>;
225
+ /**
226
+ * @throws {Error} When validation fails
227
+ */
228
+ export declare function cloneAggregateCapabilities<TIdFrom, TIdTo>(sourceAggregate: AggregateRoot<TIdFrom>, targetAggregate: AggregateRoot<TIdTo>, options?: {
229
+ includeSnapshot?: boolean;
230
+ includeVersioning?: boolean;
231
+ includeAudit?: boolean;
232
+ includeEventSourcing?: boolean;
233
+ }): void;
@@ -0,0 +1,76 @@
1
+ import { EntityId } from '../../../contracts/src/index.ts';
2
+ /**
3
+ * Base class for domain entities — objects with identity that may be
4
+ * referenced from other aggregates but are NOT themselves aggregate roots.
5
+ *
6
+ * Per Eric Evans (Blue Book, ch. 5):
7
+ * > Some objects are not defined primarily by their attributes. They
8
+ * > represent a thread of identity that runs through time and often across
9
+ * > distinct representations. Sometimes such an object must be matched with
10
+ * > another object even though attributes differ.
11
+ *
12
+ * Use `Entity<TId>` for inner objects of an aggregate that have their own
13
+ * identity (e.g. `OrderLine` inside `Order`, `Address` inside `Customer`
14
+ * if addresses are independently identifiable). Use `AggregateRoot<TId>`
15
+ * for the *root* entity of an aggregate — the transactional boundary.
16
+ *
17
+ * Two entities are equal iff they have the same `EntityId<TId>`. Attribute
18
+ * equality is irrelevant; even if all fields match, two entities with
19
+ * different ids are different things. Conversely, the same entity at two
20
+ * points in time may have different attribute values but is still the
21
+ * same entity.
22
+ *
23
+ * VF-CANON-001 (2026-05-09): added to close the canonical Evans/Vernon
24
+ * gap — competing libraries (NestJS DDD plugins, dddsample-java) ship
25
+ * a separate Entity base; this library previously only had AggregateRoot.
26
+ *
27
+ * **Relationship to `AggregateRoot`:** Conceptually, every aggregate root
28
+ * is a special kind of entity (per Evans: "the only member of its AGGREGATE
29
+ * that outside objects are allowed to hold references to"). For backward
30
+ * compatibility, `AggregateRoot` does NOT extend this `Entity` class — both
31
+ * carry their own `_id: EntityId<TId>` and `equals()` logic. Treat them as
32
+ * conceptual siblings: identity-based equality is the same contract, but
33
+ * the inheritance graph is intentionally flat to avoid breaking existing
34
+ * `AggregateRoot` consumers. A future major version may unify them.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * import { Entity } from '@vytches/ddd-aggregates';
39
+ * import { EntityId } from '@vytches/ddd-value-objects';
40
+ *
41
+ * class OrderLine extends Entity<string> {
42
+ * constructor(
43
+ * id: EntityId<string>,
44
+ * public readonly sku: string,
45
+ * public quantity: number,
46
+ * ) {
47
+ * super(id);
48
+ * }
49
+ * }
50
+ *
51
+ * const a = new OrderLine(EntityId.fromUUID('...'), 'SKU-1', 1);
52
+ * const b = new OrderLine(EntityId.fromUUID('...'), 'SKU-1', 99);
53
+ * a.equals(b); // true if ids match — quantity doesn't matter
54
+ * ```
55
+ *
56
+ * @public
57
+ * @stable
58
+ * @since 0.25.0
59
+ */
60
+ export declare abstract class Entity<TId = string> {
61
+ protected readonly _id: EntityId<TId>;
62
+ constructor(id: EntityId<TId>);
63
+ /**
64
+ * Return this entity's unique identifier.
65
+ */
66
+ getId(): EntityId<TId>;
67
+ /**
68
+ * Identity-based equality. Two entities are equal iff they have the same
69
+ * id (compared via the EntityId's own equality). Attribute differences
70
+ * are ignored.
71
+ *
72
+ * @param other - Entity to compare against. May be `null` or `undefined`.
73
+ * @returns `true` if both entities have the same id; `false` otherwise.
74
+ */
75
+ equals(other: Entity<TId> | null | undefined): boolean;
76
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const T=require("/home/runner/work/ddd/ddd/packages/logging/dist/index.js"),c=require("/home/runner/work/ddd/ddd/packages/domain-primitives/dist/index.js");function D(){return globalThis.crypto.randomUUID()}function I(a,t,e){return{eventName:a,payload:t,metadata:{eventId:D(),timestamp:new Date,...e}}}class p{isType(t){return this.type===t}static get capabilityType(){throw new Error("Capability subclass must override static capabilityType getter")}}class ${constructor(){this.capabilities=new Map}register(t){return this.capabilities.set(t.type,t),this}get(t){const e=t.capabilityType;return this.capabilities.get(e)}getByType(t){return this.capabilities.get(t)}has(t){const e=t.capabilityType;return this.capabilities.has(e)}hasByType(t){return this.capabilities.has(t)}remove(t){const e=t.capabilityType;return this.capabilities.delete(e)}removeByType(t){return this.capabilities.delete(t)}getAll(){return Array.from(this.capabilities.values())}getTypes(){return Array.from(this.capabilities.keys())}clear(){this.capabilities.clear()}get size(){return this.capabilities.size}}class g{constructor(t,e="text"){this.value=t,this.type=e}getValue(){return this.value}getType(){return this.type}validate(t){return t!=null&&t!==void 0}equals(t){return t.getValue()===this.value&&t.getType()===this.type}isType(t){return this.type===t}toString(){return String(this.value)}toJSON(){return JSON.stringify({value:this.value,type:this.type})}static create(){const t="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const i=Math.random()*16|0;return(e==="x"?i:i&3|8).toString(16)});return new g(t,"uuid")}static createText(t){return new g(t,"text")}static createUuid(t){return new g(t,"uuid")}static createInteger(t){return new g(t.toString(),"integer")}static createBigInt(t){const e=typeof t=="bigint"?t.toString():t;return new g(e,"bigint")}static createWithRandomUUID(){return g.create()}static fromUUID(t){if(!t||typeof t!="string")throw new Error("entity identifier must be provided");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))throw new Error("entity identifier must be valid UUID");return new g(t,"uuid")}static fromInteger(t){if(typeof t!="number"||t<0||!Number.isInteger(t))throw new Error("entity identifier must be a non-negative integer");return new g(t.toString(),"integer")}static fromBigInt(t){const e=typeof t=="bigint"?t.toString():t;if(!e||typeof e!="string")throw new Error("entity identifier must be a valid bigint");return new g(e,"bigint")}static fromText(t){if(!t||typeof t!="string")throw new Error("entity identifier must be provided");if(!/^[a-zA-Z0-9_-]+$/.test(t))throw new Error("entity identifier contains invalid characters");return new g(t,"text")}}class o{constructor(t,e,i){this._isSuccess=t,this._value=e,this._error=i}get isSuccess(){return this._isSuccess}get isFailure(){return!this._isSuccess}get value(){if(!this._isSuccess)throw new Error("Cannot get value of a failure result");return this._value}get error(){if(this._isSuccess)throw new Error("Cannot get error of a success result");return this._error}static ok(t){return new o(!0,t)}static empty(){return new o(!0,void 0)}static fail(t){return new o(!1,void 0,t)}static try(t){try{return o.ok(t())}catch(e){return o.fail(e instanceof Error?e:new Error(String(e)))}}map(t){return this.isFailure?o.fail(this.error):o.ok(t(this.value))}flatMap(t){return this.isFailure?o.fail(this.error):t(this.value)}match(t,e){return this.isSuccess?t(this.value):e(this.error)}tap(t){return this.isSuccess&&t(this.value),this}tapError(t){return this.isFailure&&t(this.error),this}mapError(t){return this.isSuccess?o.ok(this.value):o.fail(t(this.error))}static async tryAsync(t){try{const e=await t();return o.ok(e)}catch(e){return o.fail(e instanceof Error?e:new Error(String(e)))}}async mapAsync(t){if(this.isFailure)return o.fail(this.error);try{const e=await t(this.value);return o.ok(e)}catch(e){const i=e instanceof Error?e:new Error(String(e));return o.fail(i)}}async flatMapAsync(t){return this.isFailure?o.fail(this.error):await t(this.value)}}class d extends p{constructor(){super(...arguments),this.type="versioning",this.upcasters=new Map}static get capabilityType(){return"versioning"}attach(t){this.aggregate=t}detach(){this.aggregate=void 0,this.upcasters.clear()}registerUpcaster(t,e,i){this.upcasters.has(t)||this.upcasters.set(t,new Map),this.upcasters.get(t).set(e,i)}handleVersionedEvent(t,e){const i=t.metadata?.version||1,r=t.metadata?.targetVersion||i;let s=t;if(i<r&&this.upcasters.has(t.eventName)){const h=this.upcasters.get(t.eventName),m=T.Logger.forContext("VersioningCapability");for(let l=i;l<r;l++){const S=h.get(l);if(!S){m.warn("Missing upcaster for event version — event will be processed without upcast",{eventName:t.eventName,fromVersion:l,toVersion:l+1});continue}const _=S.upcast(s.payload,s.metadata);s={...s,payload:_,metadata:{...s.metadata,version:l+1}}}}const u=e.get(s.eventName);u&&u(s.payload,s.metadata)}getRegisteredEventTypes(){return Array.from(this.upcasters.keys())}hasUpcaster(t,e){return this.upcasters.get(t)?.has(e)||!1}getUpcastersForType(t){return this.upcasters.get(t)}clearUpcastersForType(t){this.upcasters.delete(t)}getTotalUpcasterCount(){let t=0;for(const e of this.upcasters.values())t+=e.size;return t}}class f{constructor(t){this._version=0,this._initialVersion=0,this._domainEvents=[],this._eventHandlers=new Map,this._capabilities=new $,this._id=t.id,this._version=t.version||0,this._initialVersion=t.version||0,this._maxEvents=t.maxEvents}getId(){return this._id}getVersion(){return this._version}getInitialVersion(){return this._initialVersion}hasChanges(){return this._domainEvents.length>0}getDomainEvents(){return[...this._domainEvents]}commit(){this._domainEvents=[],this._initialVersion=this._version}registerEventHandler(t,e){return this._eventHandlers.set(t,e),this}apply(t,e,i){this._version++;let r,s;typeof t=="string"?(r=I(t,e,i),s=r.metadata):(r=t,s=i?{...r.metadata,...i}:r.metadata);const u=f.sanitizeMetadata({...s,aggregateId:this._id.toString(),aggregateType:this.constructor.name,aggregateVersion:this._version,timestamp:s?.timestamp??new Date}),h=Object.assign(Object.create(Object.getPrototypeOf(r)),r,{metadata:u});if(this._maxEvents!==void 0&&this._domainEvents.length>=this._maxEvents)throw new Error(`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.`);this._domainEvents.push(h),this.handleEvent(h)}handleEvent(t){const e=this.getCapability(d);if(e)e.handleVersionedEvent(t,this._eventHandlers);else{const i=this._eventHandlers.get(t.eventName);i&&i(t.payload,t.metadata)}}loadFromHistory(t){this._version=this._initialVersion,this._domainEvents=[];for(const e of t)this.handleEvent(e),this._version++;this._initialVersion=this._version}addCapability(t){return"attach"in t&&typeof t.attach=="function"&&t.attach(this),this._capabilities.register(t),this}getCapability(t){return this._capabilities.get(t)}hasCapability(t){return this._capabilities.has(t)}removeCapability(t){const e=this._capabilities.get(t);return e&&"detach"in e&&typeof e.detach=="function"&&e.detach(),this._capabilities.remove(t),this}getAllCapabilities(){return this._capabilities.getAll()}getCapabilityTypes(){return this._capabilities.getTypes()}static sanitizeMetadata(t){if(!Object.prototype.hasOwnProperty.call(t,"__proto__")&&!Object.prototype.hasOwnProperty.call(t,"constructor")&&!Object.prototype.hasOwnProperty.call(t,"prototype"))return t;const e={};for(const i of Object.keys(t))i==="__proto__"||i==="constructor"||i==="prototype"||(e[i]=t[i]);return e}_internal_setState(t){this._version=t.version,this._initialVersion=t.initialVersion,this._domainEvents=[...t.domainEvents]}}class y extends p{constructor(){super(...arguments),this.type="audit",this.auditLog=[]}static get capabilityType(){return"audit"}attach(t){this.aggregate=t;const e=this.aggregate;typeof e.apply=="function"&&(this.originalApply=e.apply,this.originalApply&&(this.aggregate.apply=(...i)=>{const r=this.originalApply.call(this.aggregate,...i);return this.recordAuditEvent(),r}))}detach(){this.aggregate&&this.originalApply&&(this.aggregate.apply=this.originalApply),this.aggregate=void 0,this.auditLog=[],this.originalApply=void 0}getAuditLog(){return[...this.auditLog]}clearAuditLog(){this.auditLog=[]}getAuditStatistics(){const t={};let e=0;this.auditLog.forEach((r,s)=>{if(t[r.eventName]=(t[r.eventName]||0)+1,s>0){const u=this.auditLog[s-1];if(u){const h=new Date(u.timestamp).getTime(),m=new Date(r.timestamp).getTime();e+=m-h}}});const i=this.auditLog.length>1?e/(this.auditLog.length-1):0;return{totalEvents:this.auditLog.length,eventsByType:t,averageTimeBetweenEvents:i}}recordAuditEvent(){const t=this.aggregate.getDomainEvents(),e=t[t.length-1];e&&this.recordEvent(e)}recordEvent(t){const e={eventId:t.metadata?.eventId||`audit-${Date.now()}-${Math.random()}`,eventName:t.eventName,aggregateId:this.aggregate.getId().toString(),aggregateType:this.aggregate.constructor.name,aggregateVersion:this.aggregate.getVersion(),timestamp:new Date,payload:t.payload,metadata:{...t.metadata,auditCapability:!0},actor:t.metadata?.userId};this.auditLog.push(e)}getEventsByName(t){return this.auditLog.filter(e=>e.eventName===t)}getEventsByTimeRange(t,e){return this.auditLog.filter(i=>{const r=new Date(i.timestamp).getTime();return r>=t.getTime()&&r<=e.getTime()})}getFirstEvent(){return this.auditLog[0]||null}getLastEvent(){return this.auditLog[this.auditLog.length-1]||null}}class n extends c.IDomainError{static invalidArguments(t,e){const i={code:c.DomainErrorCode.InvalidParameter,data:e};return new n(t,i)}static versionConflict(t,e,i,r){const s=`Version conflict: Aggregate ${t} with ID ${e} has version ${i}, but expected ${r}`,u={code:c.DomainErrorCode.ValidationFailed,data:{aggregateType:t,aggregateId:e,currentVersion:i,expectedVersion:r}};return new n(s,u)}static featureNotEnabled(t){const e=`Feature '${t}' is not enabled on aggregate`,i={code:c.DomainErrorCode.ValidationFailed,data:{feature:t}};return new n(e,i)}static methodNotImplemented(t,e){const i=`Method '${t}' must be implemented by ${e} to use this feature`,r={code:c.DomainErrorCode.MissingValue,data:{methodName:t,aggregateType:e}};return new n(i,r)}static invalidSnapshot(t,e){const i=`Invalid snapshot for aggregate ${t}${e?`: ${e}`:""}`,r={code:c.DomainErrorCode.InvalidFormat,data:{aggregateType:t,reason:e}};return new n(i,r)}static idMismatch(t,e){const i=`ID mismatch: Snapshot is for ID ${t}, but aggregate has ID ${e}`,r={code:c.DomainErrorCode.ValidationFailed,data:{snapshotId:t,aggregateId:e}};return new n(i,r)}static typeMismatch(t,e){const i=`Aggregate type mismatch: Snapshot is for type ${t}, but trying to load into ${e}`,r={code:c.DomainErrorCode.ValidationFailed,data:{snapshotType:t,aggregateType:e}};return new n(i,r)}static duplicateUpcaster(t,e){const i=`Upcaster for event ${t} version ${e} already exists`,r={code:c.DomainErrorCode.DuplicateEntry,data:{eventType:t,sourceVersion:e}};return new n(i,r)}static missingUpcaster(t,e,i){const r=`Missing upcaster for event ${t} from version ${e} to ${i}`,s={code:c.DomainErrorCode.MissingValue,data:{eventType:t,fromVersion:e,toVersion:i}};return new n(r,s)}static configurationError(t,e){const i=`Configuration error in ${t}: ${e}`,r={code:c.DomainErrorCode.ValidationFailed,data:{aggregateType:t,configurationError:e}};return new n(i,r)}static cannotInterceptApplyMethod(t,e){const i=`Cannot intercept apply method - method not found on aggregate ${t}`,r={code:c.DomainErrorCode.InternalError,data:{aggergateId:t,...e}};return new n(i,r)}static eventStoreNotConfigured(t){const e="Event store not configured",i={code:c.DomainErrorCode.MissingValue,data:t};return new n(e,i)}static aggregateDoesNotSupportReplay(t,e){const i=`Aggregate ${t} does not support event replay`,r={code:c.DomainErrorCode.MethodNotSupported,data:{aggregateType:t,...e}};return new n(i,r)}}class b extends p{constructor(){super(...arguments),this.type="eventSourcing",this.eventStore=null}static get capabilityType(){return"eventSourcing"}attach(t){this.aggregate=t}detach(){this.aggregate=void 0,this.eventStore=null}setEventStore(t){this.eventStore=t}getEventStore(){return this.eventStore}async loadFromEventStore(t){if(!this.eventStore)throw n.configurationError(this.aggregate.constructor.name,"Event store not configured");const e=await this.eventStore.getEvents(t);if(e.length===0)return;const i=this.aggregate;i.loadFromHistory&&i.loadFromHistory(e)}async saveToEventStore(){if(!this.eventStore)throw n.configurationError(this.aggregate.constructor.name,"Event store not configured");const t=this.aggregate.getDomainEvents();t.length!==0&&await this.eventStore.saveEvents(this.aggregate.getId().toString(),[...t],this.aggregate.getInitialVersion())}hasEventStore(){return this.eventStore!==null}getStreamName(){return`${this.aggregate.constructor.name}-${this.aggregate.getId().toString()}`}async loadFromVersion(t,e){if(!this.eventStore)throw n.configurationError(this.aggregate.constructor.name,"Event store not configured");const i=await this.eventStore.getEventsAfterVersion(t,e),r=this.aggregate;r.loadFromHistory&&i.length>0&&r.loadFromHistory(i)}}class w extends p{constructor(){super(...arguments),this.type="snapshot",this._snapshot=null}static get capabilityType(){return"snapshot"}attach(t){this.aggregate=t}detach(){this.aggregate=void 0,this._snapshot=null}createSnapshot(t,e){const i=this.aggregate.getDomainEvents(),s=(i.length>0?i[i.length-1]:null)?.metadata?.eventId,h={aggregateId:this.aggregate.getId().toString(),version:this.aggregate.getVersion(),aggregateType:this.aggregate.constructor.name,state:t(),timestamp:new Date,...s!==void 0&&{lastEventId:s}};return e&&(h.metadata=e()),h}restoreFromSnapshot(t,e,i){if(!t||!t.state)throw n.invalidSnapshot(this.aggregate.constructor.name,"Invalid snapshot");const r=this.aggregate.getId().toString();if(t.aggregateId!==r)throw n.idMismatch(t.aggregateId,r);if(t.aggregateType!==this.aggregate.constructor.name)throw n.typeMismatch(t.aggregateType,this.aggregate.constructor.name);e(t.state),t.metadata&&i&&i(t.metadata);const s=this.aggregate;s._internal_setState&&s._internal_setState({version:t.version,initialVersion:t.version,domainEvents:[]})}saveTemporaryState(t){this._snapshot=this.createSnapshot(()=>t)}getLastSnapshotTimestamp(){return this._snapshot?.timestamp||null}saveSnapshot(t,e){this._snapshot=this.createSnapshot(t,e)}getPreviousState(){const t=this._snapshot;return this._snapshot=null,t}}class v{constructor(t){this.capabilities=[],this.params=t}static create(t){const e={id:t.id instanceof g?t.id:new g(t.id,"text"),version:t.version||0};return new v(e)}withSnapshots(){return this.capabilities.push({capability:new w}),this}withVersioning(){return this.capabilities.push({capability:new d}),this}withAudit(){return this.capabilities.push({capability:new y}),this}withEventSourcing(t){const e=new b;return this.capabilities.push({capability:e,configure:i=>{const r=i;t?r.setEventStore(t):this.eventStore&&r.setEventStore(this.eventStore)}}),this}withCapability(t,e){return this.capabilities.push({capability:t,configure:e?i=>e(i):void 0}),this}setEventStore(t){return this.eventStore=t,this}build(t){const e=t||f,i=new e(this.params);for(const{capability:r,configure:s}of this.capabilities)i.addCapability(r),s&&s(r);return i}buildWithAllCapabilities(t){return this.withSnapshots().withVersioning().withAudit().withEventSourcing().build(t)}}function N(a){return v.create(a)}class E{constructor(t){this._id=t}getId(){return this._id}equals(t){return t==null?!1:t===this?!0:t instanceof E?this._id.equals(t._id):!1}}function A(a){return a.hasCapability(w)}function C(a){return a.hasCapability(d)}function x(a){return a.hasCapability(y)}function V(a){return a.hasCapability(b)}function M(a){if(!A(a))throw n.featureNotEnabled("snapshot");return a}function F(a){return A(a)?o.ok(a):o.fail(n.featureNotEnabled("snapshot"))}function L(a){if(!C(a))throw n.featureNotEnabled("versioning");return a}function U(a){return C(a)?o.ok(a):o.fail(n.featureNotEnabled("versioning"))}function k(a){if(!x(a))throw n.featureNotEnabled("audit");return a}function B(a){return x(a)?o.ok(a):o.fail(n.featureNotEnabled("audit"))}function O(a){if(!V(a))throw n.featureNotEnabled("eventSourcing");return a}function j(a){return V(a)?o.ok(a):o.fail(n.featureNotEnabled("eventSourcing"))}function H(a){return a.getCapabilityTypes()}function P(a,t){return t.every(e=>a.hasCapability(e))}exports.AggregateBuilder=v;exports.AggregateError=n;exports.AggregateRoot=f;exports.AuditCapability=y;exports.Entity=E;exports.EventSourcingCapability=b;exports.SnapshotCapability=w;exports.VersioningCapability=d;exports.aggregateBuilder=N;exports.asAuditAggregate=k;exports.asEventSourcingAggregate=O;exports.asSnapshotAggregate=M;exports.asVersioningAggregate=L;exports.getAggregateCapabilities=H;exports.hasAllCapabilities=P;exports.tryAsAuditAggregate=B;exports.tryAsEventSourcingAggregate=j;exports.tryAsSnapshotAggregate=F;exports.tryAsVersioningAggregate=U;
@@ -0,0 +1,11 @@
1
+ export { AggregateRoot } from './core/aggregate-root';
2
+ export { AggregateBuilder, aggregateBuilder } from './core/aggregate-root.builder';
3
+ export { Entity } from './core/entity';
4
+ export { AuditCapability } from './capabilities/audit-capability';
5
+ export { EventSourcingCapability } from './capabilities/event-sourcing-capability';
6
+ export { SnapshotCapability } from './capabilities/snapshot-capability';
7
+ export { VersioningCapability } from './capabilities/versioning-capability';
8
+ export type { IAggregateRoot, IAggregateCapability, IAggregateConstructorParams, IAggregateEventHandler, IAggregateBuilder, } from './aggregate-interfaces';
9
+ export { AggregateError } from './aggregate-errors';
10
+ export type { AggregateWithSnapshotCapability, AggregateWithVersioningCapability, AggregateWithAuditCapability, AggregateWithEventSourcingCapability, } from './core/aggregate-utilities';
11
+ export { asSnapshotAggregate, tryAsSnapshotAggregate, asVersioningAggregate, tryAsVersioningAggregate, asAuditAggregate, tryAsAuditAggregate, asEventSourcingAggregate, tryAsEventSourcingAggregate, getAggregateCapabilities, hasAllCapabilities, } from './core/aggregate-utilities';