@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
|
@@ -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;
|
package/dist/index.d.ts
ADDED
|
@@ -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';
|