@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,58 @@
|
|
|
1
|
+
import { DomainErrorOptions, IDomainError } from '@vytches/ddd-domain-primitives';
|
|
2
|
+
/**
|
|
3
|
+
* Comprehensive error classes for aggregate-specific domain errors
|
|
4
|
+
*/
|
|
5
|
+
export declare class AggregateError extends IDomainError {
|
|
6
|
+
/**
|
|
7
|
+
* Error for invalid arguments provided to the aggregate
|
|
8
|
+
*/
|
|
9
|
+
static invalidArguments(message: string, data?: DomainErrorOptions): AggregateError;
|
|
10
|
+
/**
|
|
11
|
+
* Error for version conflicts in the aggregate
|
|
12
|
+
*/
|
|
13
|
+
static versionConflict(aggregateType: string, aggregateId: string | number, currentVersion: number, expectedVersion: number): AggregateError;
|
|
14
|
+
/**
|
|
15
|
+
* Error for when a required feature is not enabled
|
|
16
|
+
*/
|
|
17
|
+
static featureNotEnabled(feature: string): AggregateError;
|
|
18
|
+
/**
|
|
19
|
+
* Error for when a required method is not implemented
|
|
20
|
+
*/
|
|
21
|
+
static methodNotImplemented(methodName: string, aggregateType: string): AggregateError;
|
|
22
|
+
/**
|
|
23
|
+
* Error for invalid snapshot
|
|
24
|
+
*/
|
|
25
|
+
static invalidSnapshot(aggregateType: string, reason?: string): AggregateError;
|
|
26
|
+
/**
|
|
27
|
+
* Error for ID mismatch during snapshot restoration
|
|
28
|
+
*/
|
|
29
|
+
static idMismatch(snapshotId: string | number, aggregateId: string | number): AggregateError;
|
|
30
|
+
/**
|
|
31
|
+
* Error for type mismatch during snapshot restoration
|
|
32
|
+
*/
|
|
33
|
+
static typeMismatch(snapshotType: string, aggregateType: string): AggregateError;
|
|
34
|
+
/**
|
|
35
|
+
* Error for duplicate upcaster registration
|
|
36
|
+
*/
|
|
37
|
+
static duplicateUpcaster(eventType: string, sourceVersion: number): AggregateError;
|
|
38
|
+
/**
|
|
39
|
+
* Error for missing upcaster
|
|
40
|
+
*/
|
|
41
|
+
static missingUpcaster(eventType: string, fromVersion: number, toVersion: number): AggregateError;
|
|
42
|
+
/**
|
|
43
|
+
* Error for configuration problems
|
|
44
|
+
*/
|
|
45
|
+
static configurationError(aggregateType: string, message: string): AggregateError;
|
|
46
|
+
/**
|
|
47
|
+
* Error for when apply method interception fails (e.g. method not found)
|
|
48
|
+
*/
|
|
49
|
+
static cannotInterceptApplyMethod(aggergateId: string, data?: DomainErrorOptions): AggregateError;
|
|
50
|
+
/**
|
|
51
|
+
* Error for when event store is not configured
|
|
52
|
+
*/
|
|
53
|
+
static eventStoreNotConfigured(data?: DomainErrorOptions): AggregateError;
|
|
54
|
+
/**
|
|
55
|
+
* Error for when aggregate does not support event replay
|
|
56
|
+
*/
|
|
57
|
+
static aggregateDoesNotSupportReplay(aggregateType: string, data?: DomainErrorOptions): AggregateError;
|
|
58
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { Capability, CapabilityConstructor, EntityId, IAggregateSnapshot, IAuditEvent, IDomainEvent, IEventMetadata, IEventStore, IEventUpcaster } from '@vytches/ddd-contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Core interface for aggregate root functionality.
|
|
4
|
+
* Provides the foundation for domain aggregates with event sourcing, versioning, and capability management.
|
|
5
|
+
*/
|
|
6
|
+
export interface IAggregateRoot<TId = string> {
|
|
7
|
+
/**
|
|
8
|
+
* Returns the aggregate identifier
|
|
9
|
+
* @returns The unique identifier for this aggregate
|
|
10
|
+
*/
|
|
11
|
+
getId(): EntityId<TId>;
|
|
12
|
+
/**
|
|
13
|
+
* Returns the current version of the aggregate
|
|
14
|
+
* @returns Current version number for optimistic locking
|
|
15
|
+
*/
|
|
16
|
+
getVersion(): number;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the initial version when the aggregate was loaded
|
|
19
|
+
* @returns Initial version number when loaded from storage
|
|
20
|
+
*/
|
|
21
|
+
getInitialVersion(): number;
|
|
22
|
+
/**
|
|
23
|
+
* Checks if the aggregate has uncommitted changes
|
|
24
|
+
* @returns True if there are uncommitted domain events
|
|
25
|
+
*/
|
|
26
|
+
hasChanges(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Returns uncommitted domain events
|
|
29
|
+
* @returns Readonly array of uncommitted domain events
|
|
30
|
+
*/
|
|
31
|
+
getDomainEvents(): ReadonlyArray<IDomainEvent>;
|
|
32
|
+
/**
|
|
33
|
+
* Clears all uncommitted domain events and updates initial version
|
|
34
|
+
*/
|
|
35
|
+
commit(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Adds a capability to the aggregate
|
|
38
|
+
* @param capability - The capability instance to add
|
|
39
|
+
* @returns The aggregate instance for method chaining
|
|
40
|
+
*/
|
|
41
|
+
addCapability<T extends Capability & IAggregateCapability>(capability: T): this;
|
|
42
|
+
/**
|
|
43
|
+
* Gets a specific capability by its constructor
|
|
44
|
+
* @param CapabilityClass - Constructor of the capability to retrieve
|
|
45
|
+
* @returns The capability instance or undefined if not found
|
|
46
|
+
*/
|
|
47
|
+
getCapability<T extends Capability & IAggregateCapability>(CapabilityClass: CapabilityConstructor<T>): T | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Checks if aggregate has a specific capability
|
|
50
|
+
* @param CapabilityClass - Constructor of the capability to check
|
|
51
|
+
* @returns True if the capability is present
|
|
52
|
+
*/
|
|
53
|
+
hasCapability<T extends Capability & IAggregateCapability>(CapabilityClass: CapabilityConstructor<T>): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Removes a capability from the aggregate
|
|
56
|
+
* @param {CapabilityClass} - Constructor of the capability to remove
|
|
57
|
+
* @returns The aggregate instance for method chaining
|
|
58
|
+
*/
|
|
59
|
+
removeCapability<T extends Capability & IAggregateCapability>(CapabilityClass: CapabilityConstructor<T>): this;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parameters for aggregate construction.
|
|
63
|
+
* Defines the minimal required data to create an aggregate instance.
|
|
64
|
+
*/
|
|
65
|
+
export interface IAggregateConstructorParams<TId = string> {
|
|
66
|
+
/** Unique identifier for the aggregate */
|
|
67
|
+
id: EntityId<TId>;
|
|
68
|
+
/** Initial version number, defaults to 0 */
|
|
69
|
+
version?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Optional advisory limit on uncommitted domain events. If `apply()` is
|
|
72
|
+
* called when `_domainEvents.length >= maxEvents`, an error is thrown.
|
|
73
|
+
*
|
|
74
|
+
* Use to guard against runaway loops or replays of corrupted/malicious
|
|
75
|
+
* event streams that could allocate unbounded memory. A typical value is
|
|
76
|
+
* 10_000 — high enough that legitimate aggregates never hit it, low
|
|
77
|
+
* enough that a bug allocates seconds of memory, not gigabytes.
|
|
78
|
+
*
|
|
79
|
+
* REL-007 (2026-05-08): added as a defensive default-off safeguard.
|
|
80
|
+
* Leave undefined for "no limit" (preserves backward compatibility).
|
|
81
|
+
*/
|
|
82
|
+
maxEvents?: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Base interface for all aggregate capabilities.
|
|
86
|
+
* Capabilities extend aggregate functionality with features like snapshots, versioning, and event sourcing.
|
|
87
|
+
*/
|
|
88
|
+
export interface IAggregateCapability {
|
|
89
|
+
/**
|
|
90
|
+
* Called when capability is attached to an aggregate
|
|
91
|
+
* @param aggregate The aggregate to attach to
|
|
92
|
+
*/
|
|
93
|
+
attach(aggregate: IAggregateRoot<unknown>): void;
|
|
94
|
+
/**
|
|
95
|
+
* Called when capability is detached from an aggregate
|
|
96
|
+
*/
|
|
97
|
+
detach?(): void;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Capability for creating and restoring aggregate snapshots.
|
|
101
|
+
* Enables performance optimization by periodically saving aggregate state.
|
|
102
|
+
*/
|
|
103
|
+
export interface ISnapshotCapability<TState = unknown, TMeta = unknown> extends IAggregateCapability {
|
|
104
|
+
/**
|
|
105
|
+
* Creates a snapshot of the current aggregate state
|
|
106
|
+
*/
|
|
107
|
+
createSnapshot(serializer: () => TState, metadataCreator?: () => TMeta): IAggregateSnapshot<TState, TMeta>;
|
|
108
|
+
/**
|
|
109
|
+
* Restores aggregate state from a snapshot
|
|
110
|
+
*/
|
|
111
|
+
restoreFromSnapshot(snapshot: IAggregateSnapshot<TState, TMeta>, deserializer: (state: TState) => void, metadataRestorer?: (metadata: TMeta) => void): void;
|
|
112
|
+
/**
|
|
113
|
+
* Saves current state temporarily (for audit purposes)
|
|
114
|
+
*/
|
|
115
|
+
saveSnapshot(serializer: () => TState, metadataCreator?: () => TMeta): void;
|
|
116
|
+
/**
|
|
117
|
+
* Gets and clears the previous state
|
|
118
|
+
*/
|
|
119
|
+
getPreviousState(): unknown | null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Capability for handling event versioning and upcasting.
|
|
123
|
+
* Enables backward compatibility when event schemas evolve.
|
|
124
|
+
*/
|
|
125
|
+
export interface IVersioningCapability extends IAggregateCapability {
|
|
126
|
+
/**
|
|
127
|
+
* Registers an upcaster for a specific event type and version
|
|
128
|
+
*/
|
|
129
|
+
registerUpcaster<TFrom = unknown, TTo = unknown>(eventType: string, sourceVersion: number, upcaster: IEventUpcaster<TFrom, TTo>): this;
|
|
130
|
+
/**
|
|
131
|
+
* Handles versioned event processing
|
|
132
|
+
*/
|
|
133
|
+
handleVersionedEvent(event: IDomainEvent, handlers: Map<string, IAggregateEventHandler>): void;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Capability for event store integration.
|
|
137
|
+
* Enables loading and saving aggregates from/to event stores.
|
|
138
|
+
*/
|
|
139
|
+
export interface IEventSourcingCapability extends IAggregateCapability {
|
|
140
|
+
/**
|
|
141
|
+
* Loads aggregate from event store
|
|
142
|
+
*/
|
|
143
|
+
loadFromEventStore(aggregateId: unknown): Promise<void>;
|
|
144
|
+
/**
|
|
145
|
+
* Saves aggregate events to event store
|
|
146
|
+
*/
|
|
147
|
+
saveToEventStore(): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Replays events to rebuild aggregate state
|
|
150
|
+
*/
|
|
151
|
+
replayEvents(events: IDomainEvent[]): void;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Capability for maintaining audit logs of aggregate changes.
|
|
155
|
+
* Tracks all modifications for compliance and debugging purposes.
|
|
156
|
+
*/
|
|
157
|
+
export interface IAuditCapability extends IAggregateCapability {
|
|
158
|
+
/**
|
|
159
|
+
* Gets the audit log for this aggregate
|
|
160
|
+
*/
|
|
161
|
+
getAuditLog(): ReadonlyArray<IAuditEvent>;
|
|
162
|
+
/**
|
|
163
|
+
* Clears the audit log
|
|
164
|
+
*/
|
|
165
|
+
clearAuditLog(): void;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Capability for adding middleware to event processing pipeline.
|
|
169
|
+
* Enables cross-cutting concerns like validation, logging, and transformation.
|
|
170
|
+
*/
|
|
171
|
+
export interface IMiddlewareCapability extends IAggregateCapability {
|
|
172
|
+
/**
|
|
173
|
+
* Adds middleware to the event processing pipeline
|
|
174
|
+
*/
|
|
175
|
+
use(middleware: EventAggregateMiddleware): this;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Builder interface for creating aggregates with capabilities.
|
|
179
|
+
* Provides fluent API for configuring aggregate features before construction.
|
|
180
|
+
*/
|
|
181
|
+
export interface IAggregateBuilder<TId> {
|
|
182
|
+
/**
|
|
183
|
+
* Adds snapshot capability
|
|
184
|
+
*/
|
|
185
|
+
withSnapshots<TState = unknown, TMeta = unknown>(): this;
|
|
186
|
+
/**
|
|
187
|
+
* Adds versioning capability
|
|
188
|
+
*/
|
|
189
|
+
withVersioning(): this;
|
|
190
|
+
/**
|
|
191
|
+
* Adds event sourcing capability
|
|
192
|
+
*/
|
|
193
|
+
withEventSourcing(eventStore?: IEventStore): this;
|
|
194
|
+
/**
|
|
195
|
+
* Adds audit capability
|
|
196
|
+
*/
|
|
197
|
+
withAudit(): this;
|
|
198
|
+
/**
|
|
199
|
+
* Adds custom capability
|
|
200
|
+
*/
|
|
201
|
+
withCustomCapability<T extends IAggregateCapability>(name: string, capability: T): this;
|
|
202
|
+
/**
|
|
203
|
+
* Builds the aggregate with all configured capabilities
|
|
204
|
+
*/
|
|
205
|
+
build(): IAggregateRoot<TId>;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Handler function for processing aggregate events.
|
|
209
|
+
* Used internally by aggregates to handle domain events.
|
|
210
|
+
*/
|
|
211
|
+
export interface IAggregateEventHandler<T = unknown> {
|
|
212
|
+
(payload: T, metadata?: IEventMetadata): void;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Middleware function for event processing pipeline.
|
|
216
|
+
* Enables interception and modification of events before they are handled.
|
|
217
|
+
*/
|
|
218
|
+
export type EventAggregateMiddleware<T = unknown> = (event: IDomainEvent<T>, next: (event: IDomainEvent<T>) => void) => void;
|
|
219
|
+
/**
|
|
220
|
+
* @deprecated Use capability constructors directly instead of string names
|
|
221
|
+
* Constants for standard capability names.
|
|
222
|
+
*/
|
|
223
|
+
export declare const CAPABILITY_NAMES: {
|
|
224
|
+
readonly SNAPSHOT: "snapshot";
|
|
225
|
+
readonly VERSIONING: "versioning";
|
|
226
|
+
readonly EVENT_SOURCING: "eventSourcing";
|
|
227
|
+
readonly AUDIT: "audit";
|
|
228
|
+
readonly MIDDLEWARE: "middleware";
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* @deprecated Use capability constructors directly instead of string names
|
|
232
|
+
* Union type of all standard capability names.
|
|
233
|
+
*/
|
|
234
|
+
export type CapabilityName = (typeof CAPABILITY_NAMES)[keyof typeof CAPABILITY_NAMES];
|
|
235
|
+
/**
|
|
236
|
+
* Utility type to extract the ID type from an aggregate type.
|
|
237
|
+
*/
|
|
238
|
+
export type AggregateIdType<T> = T extends IAggregateRoot<infer TId> ? TId : never;
|
|
239
|
+
/**
|
|
240
|
+
* @deprecated Use capability constructors directly instead of string-based capabilities
|
|
241
|
+
* Utility type for aggregates with specific capabilities.
|
|
242
|
+
*/
|
|
243
|
+
export type AggregateWithCapabilities<TId, TCapabilities extends CapabilityName[]> = IAggregateRoot<TId> & {
|
|
244
|
+
[K in TCapabilities[number] as `getCapability`]: (name: K) => K extends 'snapshot' ? ISnapshotCapability : K extends 'versioning' ? IVersioningCapability : K extends 'eventSourcing' ? IEventSourcingCapability : K extends 'audit' ? IAuditCapability : K extends 'middleware' ? IMiddlewareCapability : IAggregateCapability;
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Factory interface for creating and rebuilding aggregates.
|
|
248
|
+
* Provides standard methods for aggregate lifecycle management.
|
|
249
|
+
* @template TId - The type of the aggregate ID value (string, number, etc.)
|
|
250
|
+
*/
|
|
251
|
+
export interface IAggregateFactory<TId = string, TAggregate extends IAggregateRoot<TId> = IAggregateRoot<TId>> {
|
|
252
|
+
/**
|
|
253
|
+
* Creates a new aggregate instance
|
|
254
|
+
*/
|
|
255
|
+
create(id: EntityId<TId>, ...args: unknown[]): TAggregate;
|
|
256
|
+
/**
|
|
257
|
+
* Rebuilds aggregate from events
|
|
258
|
+
*/
|
|
259
|
+
fromEvents(id: EntityId<TId>, events: IDomainEvent[]): TAggregate;
|
|
260
|
+
/**
|
|
261
|
+
* Rebuilds aggregate from snapshot and subsequent events
|
|
262
|
+
*/
|
|
263
|
+
fromSnapshot<TState, TMeta>(snapshot: IAggregateSnapshot<TState, TMeta>, events: IDomainEvent[]): TAggregate;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Interface for validating aggregate state and operations.
|
|
267
|
+
* Enables business rule validation at the aggregate level.
|
|
268
|
+
*/
|
|
269
|
+
export interface IAggregateValidator<TAggregate extends IAggregateRoot<unknown>> {
|
|
270
|
+
/**
|
|
271
|
+
* Validates aggregate state
|
|
272
|
+
*/
|
|
273
|
+
validate(aggregate: TAggregate): ValidationResult;
|
|
274
|
+
/**
|
|
275
|
+
* Validates aggregate before applying event
|
|
276
|
+
*/
|
|
277
|
+
validateBeforeEvent(aggregate: TAggregate, event: IDomainEvent): ValidationResult;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Result of aggregate validation operation.
|
|
281
|
+
* Contains validation status and any error details.
|
|
282
|
+
*/
|
|
283
|
+
export interface ValidationResult {
|
|
284
|
+
/** Whether the validation passed */
|
|
285
|
+
isValid: boolean;
|
|
286
|
+
/** List of validation errors if any */
|
|
287
|
+
errors: IValidationError[];
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Represents a single validation error.
|
|
291
|
+
* Provides details about what failed validation and why.
|
|
292
|
+
*/
|
|
293
|
+
export interface IValidationError {
|
|
294
|
+
/** The field or property that failed validation */
|
|
295
|
+
field: string;
|
|
296
|
+
/** Human-readable error message */
|
|
297
|
+
message: string;
|
|
298
|
+
/** Optional error code for programmatic handling */
|
|
299
|
+
code?: string;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Capability for caching aggregate data.
|
|
303
|
+
* Provides key-value storage with TTL support for performance optimization.
|
|
304
|
+
*/
|
|
305
|
+
export interface ICachingCapability extends IAggregateCapability {
|
|
306
|
+
/**
|
|
307
|
+
* Gets cached value
|
|
308
|
+
*/
|
|
309
|
+
get<T>(key: string): T | undefined;
|
|
310
|
+
/**
|
|
311
|
+
* Sets cached value
|
|
312
|
+
*/
|
|
313
|
+
set<T>(key: string, value: T, ttl?: number): void;
|
|
314
|
+
/**
|
|
315
|
+
* Clears all cached values
|
|
316
|
+
*/
|
|
317
|
+
clear(): void;
|
|
318
|
+
/**
|
|
319
|
+
* Invalidates specific cached value
|
|
320
|
+
*/
|
|
321
|
+
invalidate(key: string): void;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Capability for collecting aggregate metrics.
|
|
325
|
+
* Enables monitoring and observability of aggregate operations.
|
|
326
|
+
*/
|
|
327
|
+
export interface IMetricsCapability extends IAggregateCapability {
|
|
328
|
+
/**
|
|
329
|
+
* Records a metric
|
|
330
|
+
*/
|
|
331
|
+
record(name: string, value: number, tags?: Record<string, string>): void;
|
|
332
|
+
/**
|
|
333
|
+
* Increments a counter
|
|
334
|
+
*/
|
|
335
|
+
increment(name: string, tags?: Record<string, string>): void;
|
|
336
|
+
/**
|
|
337
|
+
* Records timing information
|
|
338
|
+
*/
|
|
339
|
+
timing(name: string, duration: number, tags?: Record<string, string>): void;
|
|
340
|
+
/**
|
|
341
|
+
* Gets recorded metrics
|
|
342
|
+
*/
|
|
343
|
+
getMetrics(): MetricData[];
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Represents a single metric data point.
|
|
347
|
+
* Contains metric information with optional tags and timestamp.
|
|
348
|
+
*/
|
|
349
|
+
export interface MetricData {
|
|
350
|
+
/** Metric name */
|
|
351
|
+
name: string;
|
|
352
|
+
/** Metric value */
|
|
353
|
+
value: number;
|
|
354
|
+
/** Type of metric */
|
|
355
|
+
type: 'counter' | 'gauge' | 'timing';
|
|
356
|
+
/** Optional tags for metric categorization */
|
|
357
|
+
tags?: Record<string, string>;
|
|
358
|
+
/** When the metric was recorded */
|
|
359
|
+
timestamp: Date;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Capability for security features like encryption and authorization.
|
|
363
|
+
* Provides security controls for sensitive aggregate operations.
|
|
364
|
+
*/
|
|
365
|
+
export interface ISecurityCapability extends IAggregateCapability {
|
|
366
|
+
/**
|
|
367
|
+
* Encrypts sensitive data
|
|
368
|
+
*/
|
|
369
|
+
encrypt(data: unknown): string;
|
|
370
|
+
/**
|
|
371
|
+
* Decrypts sensitive data
|
|
372
|
+
*/
|
|
373
|
+
decrypt(encryptedData: string): unknown;
|
|
374
|
+
/**
|
|
375
|
+
* Checks if user has permission to perform action
|
|
376
|
+
*/
|
|
377
|
+
hasPermission(action: string, user?: unknown): boolean;
|
|
378
|
+
/**
|
|
379
|
+
* Logs security events
|
|
380
|
+
*/
|
|
381
|
+
logSecurityEvent(event: SecurityEvent): void;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Represents a security-related event.
|
|
385
|
+
* Used for audit trails and security monitoring.
|
|
386
|
+
*/
|
|
387
|
+
export interface SecurityEvent {
|
|
388
|
+
/** Type of security event */
|
|
389
|
+
type: 'access' | 'modification' | 'failure';
|
|
390
|
+
/** Action that was performed */
|
|
391
|
+
action: string;
|
|
392
|
+
/** User who performed the action */
|
|
393
|
+
user?: unknown;
|
|
394
|
+
/** When the event occurred */
|
|
395
|
+
timestamp: Date;
|
|
396
|
+
/** Additional event metadata */
|
|
397
|
+
metadata?: Record<string, unknown>;
|
|
398
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Capability, IAuditCapability, IAuditEvent, IDomainEvent } from '../../../contracts/src/index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Capability that records every domain event applied to the aggregate as
|
|
4
|
+
* an `IAuditEvent`. Useful for compliance, debugging, behavior analytics,
|
|
5
|
+
* and "what happened" forensics — without polluting the aggregate's
|
|
6
|
+
* domain logic with cross-cutting audit concerns.
|
|
7
|
+
*
|
|
8
|
+
* Mechanism: on attach, intercepts the aggregate's `apply()` method. Every
|
|
9
|
+
* subsequent `apply(eventName, payload)` call records an `IAuditEvent`
|
|
10
|
+
* containing the aggregate id, type, version at time of event, original
|
|
11
|
+
* payload, and an actor (read from `metadata.userId` if present). The
|
|
12
|
+
* intercepted `apply` is restored on `detach()`.
|
|
13
|
+
*
|
|
14
|
+
* **Order matters.** Add this capability *before* any `apply()` calls —
|
|
15
|
+
* audit only captures events applied after attachment. The recommended
|
|
16
|
+
* way is via {@link AggregateBuilder} which guarantees correct order.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import {
|
|
21
|
+
* AggregateBuilder,
|
|
22
|
+
* AuditCapability,
|
|
23
|
+
* asAuditAggregate,
|
|
24
|
+
* } from '@vytches/ddd-aggregates';
|
|
25
|
+
*
|
|
26
|
+
* const order = AggregateBuilder
|
|
27
|
+
* .create({ id: EntityId.create() })
|
|
28
|
+
* .withAudit()
|
|
29
|
+
* .build(Order);
|
|
30
|
+
*
|
|
31
|
+
* order.apply('OrderCreated', { customerId: 'c-1' }, { userId: 'u-42' });
|
|
32
|
+
* order.apply('OrderConfirmed', undefined, { userId: 'u-42' });
|
|
33
|
+
*
|
|
34
|
+
* const audit = asAuditAggregate(order).getCapability(AuditCapability)!;
|
|
35
|
+
* audit.getAuditLog(); // [{ eventName: 'OrderCreated', actor: 'u-42', ... }, ...]
|
|
36
|
+
* audit.getEventsByName('OrderCreated');
|
|
37
|
+
* audit.getAuditStatistics(); // { totalEvents: 2, eventsByType: {...}, averageTimeBetweenEvents }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
* @stable
|
|
42
|
+
* @since 0.1.0
|
|
43
|
+
*/
|
|
44
|
+
export declare class AuditCapability extends Capability<'audit'> implements IAuditCapability {
|
|
45
|
+
readonly type: "audit";
|
|
46
|
+
static get capabilityType(): string;
|
|
47
|
+
private aggregate;
|
|
48
|
+
private auditLog;
|
|
49
|
+
private originalApply?;
|
|
50
|
+
/**
|
|
51
|
+
* @param {unknown} aggregate - Aggregate to attach this capability to
|
|
52
|
+
*/
|
|
53
|
+
attach(aggregate: unknown): void;
|
|
54
|
+
detach?(): void;
|
|
55
|
+
/**
|
|
56
|
+
* @returns {IAuditEvent[]} Copy of the current audit log
|
|
57
|
+
*/
|
|
58
|
+
getAuditLog(): IAuditEvent[];
|
|
59
|
+
clearAuditLog(): void;
|
|
60
|
+
/**
|
|
61
|
+
* @returns {{totalEvents: number, eventsByType: Record<string, number>, averageTimeBetweenEvents: number}} Audit statistics summary
|
|
62
|
+
*/
|
|
63
|
+
getAuditStatistics(): {
|
|
64
|
+
totalEvents: number;
|
|
65
|
+
eventsByType: Record<string, number>;
|
|
66
|
+
averageTimeBetweenEvents: number;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Record an audit event when domain events are added
|
|
70
|
+
*/
|
|
71
|
+
private recordAuditEvent;
|
|
72
|
+
/**
|
|
73
|
+
* @param {IDomainEvent} event - Domain event to record in audit log
|
|
74
|
+
*/
|
|
75
|
+
recordEvent(event: IDomainEvent): void;
|
|
76
|
+
/**
|
|
77
|
+
* @param {string} eventName - Name of events to filter by
|
|
78
|
+
* @returns {IAuditEvent[]} Array of audit events matching the specified name
|
|
79
|
+
*/
|
|
80
|
+
getEventsByName(eventName: string): IAuditEvent[];
|
|
81
|
+
/**
|
|
82
|
+
* @param {Date} startDate - Start date for time range filter
|
|
83
|
+
* @param {Date} endDate - End date for time range filter
|
|
84
|
+
* @returns {IAuditEvent[]} Array of audit events within the specified time range
|
|
85
|
+
*/
|
|
86
|
+
getEventsByTimeRange(startDate: Date, endDate: Date): IAuditEvent[];
|
|
87
|
+
/**
|
|
88
|
+
* @returns {IAuditEvent | null} First audit event in chronological order or null if none exists
|
|
89
|
+
*/
|
|
90
|
+
getFirstEvent(): IAuditEvent | null;
|
|
91
|
+
/**
|
|
92
|
+
* @returns {IAuditEvent | null} Last audit event in chronological order or null if none exists
|
|
93
|
+
*/
|
|
94
|
+
getLastEvent(): IAuditEvent | null;
|
|
95
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { IEventSourcingCapability, IEventStore, Capability } from '../../../contracts/src/index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Capability that integrates an aggregate with an external `IEventStore` —
|
|
4
|
+
* the persistence boundary for an event-sourced system. Allows the
|
|
5
|
+
* aggregate to load its own state from history and persist new events
|
|
6
|
+
* without coupling domain code to the storage adapter.
|
|
7
|
+
*
|
|
8
|
+
* **Important**: this library is dependency-free and ships *no* concrete
|
|
9
|
+
* `IEventStore` implementation. Provide your own (in-memory, EventStoreDB,
|
|
10
|
+
* Postgres + JSONB, Kafka topic, etc.) and pass it via
|
|
11
|
+
* {@link setEventStore}.
|
|
12
|
+
*
|
|
13
|
+
* @example Wiring up with a custom store
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import {
|
|
16
|
+
* AggregateBuilder,
|
|
17
|
+
* EventSourcingCapability,
|
|
18
|
+
* } from '@vytches/ddd-aggregates';
|
|
19
|
+
*
|
|
20
|
+
* const eventStore: IEventStore = new MyPostgresEventStore(pool);
|
|
21
|
+
*
|
|
22
|
+
* const order = AggregateBuilder
|
|
23
|
+
* .create({ id })
|
|
24
|
+
* .withEventSourcing()
|
|
25
|
+
* .build(Order);
|
|
26
|
+
*
|
|
27
|
+
* const es = order.getCapability(EventSourcingCapability)!;
|
|
28
|
+
* es.setEventStore(eventStore);
|
|
29
|
+
*
|
|
30
|
+
* await es.saveToStore(); // persists uncommitted events, increments version
|
|
31
|
+
* await es.loadFromStore(); // hydrates aggregate from stream
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
35
|
+
* @stable
|
|
36
|
+
* @since 0.1.0
|
|
37
|
+
*/
|
|
38
|
+
export declare class EventSourcingCapability extends Capability<'eventSourcing'> implements IEventSourcingCapability {
|
|
39
|
+
readonly type: "eventSourcing";
|
|
40
|
+
static get capabilityType(): string;
|
|
41
|
+
private aggregate;
|
|
42
|
+
private eventStore;
|
|
43
|
+
/**
|
|
44
|
+
* @param {unknown} aggregate - Aggregate to attach this capability to
|
|
45
|
+
*/
|
|
46
|
+
attach(aggregate: unknown): void;
|
|
47
|
+
detach?(): void;
|
|
48
|
+
/**
|
|
49
|
+
* @param {IEventStore} eventStore - Event store instance to use for persistence
|
|
50
|
+
*/
|
|
51
|
+
setEventStore(eventStore: IEventStore): void;
|
|
52
|
+
/**
|
|
53
|
+
* @returns {IEventStore | null} Currently configured event store or null
|
|
54
|
+
*/
|
|
55
|
+
getEventStore(): IEventStore | null;
|
|
56
|
+
/**
|
|
57
|
+
* @param {string | number} aggregateId - ID of aggregate to load events for
|
|
58
|
+
* @throws {AggregateError} When event store is not configured
|
|
59
|
+
*/
|
|
60
|
+
loadFromEventStore(aggregateId: string | number): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* @throws {AggregateError} When event store is not configured
|
|
63
|
+
*/
|
|
64
|
+
saveToEventStore(): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* @returns {boolean} True if event store is configured
|
|
67
|
+
*/
|
|
68
|
+
hasEventStore(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* @returns {string} Stream name combining aggregate type and ID
|
|
71
|
+
*/
|
|
72
|
+
getStreamName(): string;
|
|
73
|
+
/**
|
|
74
|
+
* @param {string | number} aggregateId - ID of aggregate to load events for
|
|
75
|
+
* @param {number} fromVersion - Version number to start loading from
|
|
76
|
+
* @throws {AggregateError} When event store is not configured
|
|
77
|
+
*/
|
|
78
|
+
loadFromVersion(aggregateId: string | number, fromVersion: number): Promise<void>;
|
|
79
|
+
}
|