@shirudo/ddd-kit 0.9.8 → 0.10.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/README.md +21 -5
- package/dist/deep-equal-except-C8yoSk4L.d.ts +57 -0
- package/dist/index.d.ts +1400 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/result-jCwPSjFa.d.ts +352 -0
- package/dist/result.d.ts +204 -0
- package/dist/result.js +2 -0
- package/dist/result.js.map +1 -0
- package/dist/utils-array.d.ts +47 -0
- package/dist/utils-array.js +2 -0
- package/dist/utils-array.js.map +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +2 -0
- package/dist/utils.js.map +1 -0
- package/package.json +3 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1400 @@
|
|
|
1
|
+
import { R as Result } from './result-jCwPSjFa.js';
|
|
2
|
+
import { a as DeepEqualExceptOptions } from './deep-equal-except-C8yoSk4L.js';
|
|
3
|
+
|
|
4
|
+
type Id<Tag extends string> = string & {
|
|
5
|
+
readonly __brand: Tag;
|
|
6
|
+
};
|
|
7
|
+
interface IdGenerator {
|
|
8
|
+
next: <T extends string>() => Id<T>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Version = number & {
|
|
12
|
+
readonly __v: true;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Metadata associated with a domain event for traceability and correlation.
|
|
16
|
+
* Used in event-driven architectures to track event flow across services.
|
|
17
|
+
*/
|
|
18
|
+
interface EventMetadata {
|
|
19
|
+
/**
|
|
20
|
+
* Correlation ID for tracing events across multiple services/components.
|
|
21
|
+
* Typically used to group related events in a distributed system.
|
|
22
|
+
*/
|
|
23
|
+
correlationId?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Causation ID referencing the event or command that caused this event.
|
|
26
|
+
* Used to build event chains and understand causality.
|
|
27
|
+
*/
|
|
28
|
+
causationId?: string;
|
|
29
|
+
/**
|
|
30
|
+
* User ID of the person or system that triggered the event.
|
|
31
|
+
*/
|
|
32
|
+
userId?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Source service or component that produced the event.
|
|
35
|
+
*/
|
|
36
|
+
source?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Additional custom metadata fields.
|
|
39
|
+
* Allows extensibility for domain-specific metadata.
|
|
40
|
+
*/
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Domain Event represents something meaningful that happened in the domain.
|
|
45
|
+
* Events are immutable and carry information about what occurred.
|
|
46
|
+
*
|
|
47
|
+
* @template T - The event type name (e.g., "OrderCreated")
|
|
48
|
+
* @template P - The event payload type
|
|
49
|
+
*/
|
|
50
|
+
interface DomainEvent<T extends string, P> {
|
|
51
|
+
/**
|
|
52
|
+
* The type of the event, used for routing and handling.
|
|
53
|
+
*/
|
|
54
|
+
type: T;
|
|
55
|
+
/**
|
|
56
|
+
* The event payload containing the domain data.
|
|
57
|
+
*/
|
|
58
|
+
payload: P;
|
|
59
|
+
/**
|
|
60
|
+
* Timestamp when the event occurred.
|
|
61
|
+
*/
|
|
62
|
+
occurredAt: Date;
|
|
63
|
+
/**
|
|
64
|
+
* Event schema version for handling schema evolution.
|
|
65
|
+
* Defaults to 1 if not specified. Higher versions indicate schema changes.
|
|
66
|
+
*/
|
|
67
|
+
version?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Optional metadata for traceability, correlation, and auditing.
|
|
70
|
+
* Includes correlationId, causationId, userId, source, and custom fields.
|
|
71
|
+
*/
|
|
72
|
+
metadata?: EventMetadata;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Marker interface for Aggregate Roots.
|
|
76
|
+
*
|
|
77
|
+
* In Domain-Driven Design, an Aggregate Root is an Entity (the parent Entity of the aggregate).
|
|
78
|
+
* It represents the aggregate externally and is the only object that external code
|
|
79
|
+
* is allowed to hold references to. All access to child entities within the aggregate
|
|
80
|
+
* must go through the Aggregate Root.
|
|
81
|
+
*
|
|
82
|
+
* An Aggregate consists of:
|
|
83
|
+
* - One Aggregate Root (Entity with id + version)
|
|
84
|
+
* - Optional child entities (Entities with id, but no own version)
|
|
85
|
+
* - Optional value objects
|
|
86
|
+
*
|
|
87
|
+
* The Aggregate Root has identity (id) and version for optimistic concurrency control.
|
|
88
|
+
* Child entities exist only within the aggregate boundary and are versioned through
|
|
89
|
+
* the Aggregate Root.
|
|
90
|
+
*
|
|
91
|
+
* @template TId - The type of the aggregate root identifier
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* class Order extends AggregateBase<OrderState, OrderId> implements AggregateRoot<OrderId> {
|
|
96
|
+
* // Order is an Aggregate Root (an Entity)
|
|
97
|
+
* // OrderState contains child entities (e.g., OrderItem) and value objects
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
interface AggregateRoot<TId extends Id<string>> {
|
|
102
|
+
/**
|
|
103
|
+
* Unique identifier of the aggregate root entity.
|
|
104
|
+
*/
|
|
105
|
+
readonly id: TId;
|
|
106
|
+
/**
|
|
107
|
+
* Version number for optimistic concurrency control.
|
|
108
|
+
* Incremented on each state change to detect concurrent modifications.
|
|
109
|
+
* This version applies to the entire aggregate, including all child entities.
|
|
110
|
+
*/
|
|
111
|
+
readonly version: Version;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Structural interface representing an aggregate with state and events.
|
|
115
|
+
* Used for type constraints in repositories and other infrastructure code.
|
|
116
|
+
*
|
|
117
|
+
* @template State - The type of the aggregate state
|
|
118
|
+
* @template Evt - The union type of all domain events
|
|
119
|
+
*/
|
|
120
|
+
interface Aggregate<State, Evt extends DomainEvent<string, unknown>> {
|
|
121
|
+
state: Readonly<State>;
|
|
122
|
+
version: Version;
|
|
123
|
+
pendingEvents: ReadonlyArray<Evt>;
|
|
124
|
+
}
|
|
125
|
+
declare function aggregate<State, Evt extends DomainEvent<string, unknown>>(state: State, version?: Version): Aggregate<State, Evt>;
|
|
126
|
+
declare function withEvent<S, E extends DomainEvent<string, unknown>>(agg: Aggregate<S, E>, evt: E): Aggregate<S, E>;
|
|
127
|
+
declare function bump<S, E extends DomainEvent<string, unknown>>(agg: Aggregate<S, E>): Aggregate<S, E>;
|
|
128
|
+
/**
|
|
129
|
+
* Creates a domain event with default values.
|
|
130
|
+
* Sets occurredAt to current date and version to 1 if not provided.
|
|
131
|
+
*
|
|
132
|
+
* @param type - The event type
|
|
133
|
+
* @param payload - The event payload
|
|
134
|
+
* @param options - Optional event configuration
|
|
135
|
+
* @returns A domain event
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* const event = createDomainEvent("OrderCreated", { orderId: "123" });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
declare function createDomainEvent<T extends string, P>(type: T, payload: P, options?: {
|
|
143
|
+
occurredAt?: Date;
|
|
144
|
+
version?: number;
|
|
145
|
+
metadata?: EventMetadata;
|
|
146
|
+
}): DomainEvent<T, P>;
|
|
147
|
+
/**
|
|
148
|
+
* Creates a domain event with metadata for traceability.
|
|
149
|
+
* Convenience function for creating events with correlation and causation IDs.
|
|
150
|
+
*
|
|
151
|
+
* @param type - The event type
|
|
152
|
+
* @param payload - The event payload
|
|
153
|
+
* @param metadata - Event metadata for traceability
|
|
154
|
+
* @param options - Optional event configuration
|
|
155
|
+
* @returns A domain event with metadata
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* const event = createDomainEventWithMetadata(
|
|
160
|
+
* "OrderCreated",
|
|
161
|
+
* { orderId: "123" },
|
|
162
|
+
* {
|
|
163
|
+
* correlationId: "corr-123",
|
|
164
|
+
* causationId: "cmd-456",
|
|
165
|
+
* userId: "user-789"
|
|
166
|
+
* }
|
|
167
|
+
* );
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
declare function createDomainEventWithMetadata<T extends string, P>(type: T, payload: P, metadata: EventMetadata, options?: {
|
|
171
|
+
occurredAt?: Date;
|
|
172
|
+
version?: number;
|
|
173
|
+
}): DomainEvent<T, P>;
|
|
174
|
+
/**
|
|
175
|
+
* Copies metadata from a source event to a new event.
|
|
176
|
+
* Useful for maintaining correlation chains in event-driven architectures.
|
|
177
|
+
*
|
|
178
|
+
* @param sourceEvent - The source event to copy metadata from
|
|
179
|
+
* @param additionalMetadata - Additional metadata to merge in
|
|
180
|
+
* @returns Event metadata with copied and merged values
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const newEvent = createDomainEvent(
|
|
185
|
+
* "OrderShipped",
|
|
186
|
+
* { orderId: "123" },
|
|
187
|
+
* {
|
|
188
|
+
* metadata: copyMetadata(previousEvent, { causationId: previousEvent.type })
|
|
189
|
+
* }
|
|
190
|
+
* );
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
declare function copyMetadata(sourceEvent: DomainEvent<string, unknown>, additionalMetadata?: Partial<EventMetadata>): EventMetadata;
|
|
194
|
+
/**
|
|
195
|
+
* Merges multiple metadata objects into one.
|
|
196
|
+
* Later metadata objects override earlier ones for the same keys.
|
|
197
|
+
*
|
|
198
|
+
* @param metadataObjects - Array of metadata objects to merge
|
|
199
|
+
* @returns Merged event metadata
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* const metadata = mergeMetadata(
|
|
204
|
+
* { correlationId: "corr-123" },
|
|
205
|
+
* { userId: "user-456" },
|
|
206
|
+
* { source: "order-service" }
|
|
207
|
+
* );
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
declare function mergeMetadata(...metadataObjects: Array<EventMetadata | undefined>): EventMetadata;
|
|
211
|
+
/**
|
|
212
|
+
* Snapshot of an aggregate state at a specific point in time.
|
|
213
|
+
* Used for optimizing event replay by starting from a snapshot
|
|
214
|
+
* instead of replaying all events from the beginning.
|
|
215
|
+
*
|
|
216
|
+
* @template TState - The type of the aggregate state
|
|
217
|
+
*/
|
|
218
|
+
interface AggregateSnapshot<TState> {
|
|
219
|
+
/**
|
|
220
|
+
* The state of the aggregate at the time of the snapshot.
|
|
221
|
+
*/
|
|
222
|
+
state: TState;
|
|
223
|
+
/**
|
|
224
|
+
* The version of the aggregate when the snapshot was taken.
|
|
225
|
+
*/
|
|
226
|
+
version: Version;
|
|
227
|
+
/**
|
|
228
|
+
* Timestamp when the snapshot was created.
|
|
229
|
+
*/
|
|
230
|
+
snapshotAt: Date;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Checks if two aggregates are the same (same ID and version).
|
|
234
|
+
* Useful for optimistic concurrency control checks.
|
|
235
|
+
*
|
|
236
|
+
* @param a - First aggregate
|
|
237
|
+
* @param b - Second aggregate
|
|
238
|
+
* @returns true if both aggregates have the same ID and version
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* const aggregate1 = await repository.getById(id);
|
|
243
|
+
* // ... some operations ...
|
|
244
|
+
* const aggregate2 = await repository.getById(id);
|
|
245
|
+
*
|
|
246
|
+
* if (!sameAggregate(aggregate1, aggregate2)) {
|
|
247
|
+
* throw new Error("Aggregate was modified by another process");
|
|
248
|
+
* }
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
declare function sameAggregate<TId extends Id<string>>(a: {
|
|
252
|
+
id: TId;
|
|
253
|
+
version: Version;
|
|
254
|
+
}, b: {
|
|
255
|
+
id: TId;
|
|
256
|
+
version: Version;
|
|
257
|
+
}): boolean;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Configuration options for AggregateBase behavior.
|
|
261
|
+
*/
|
|
262
|
+
interface AggregateConfig {
|
|
263
|
+
/**
|
|
264
|
+
* Whether to automatically bump the version when state changes.
|
|
265
|
+
* Defaults to false. Set to true for automatic versioning.
|
|
266
|
+
*/
|
|
267
|
+
autoVersionBump?: boolean;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Base class for creating Aggregate Roots (Entities) without Event Sourcing.
|
|
271
|
+
*
|
|
272
|
+
* This class creates an Entity that serves as the Aggregate Root. The Aggregate Root
|
|
273
|
+
* is the parent Entity of the aggregate and represents it externally. It has identity
|
|
274
|
+
* (id) and version for optimistic concurrency control.
|
|
275
|
+
*
|
|
276
|
+
* The aggregate state (`TState`) contains:
|
|
277
|
+
* - Child entities (Entities with id, but no own version)
|
|
278
|
+
* - Value objects (immutable objects)
|
|
279
|
+
*
|
|
280
|
+
* All changes to child entities are versioned through the Aggregate Root. The version
|
|
281
|
+
* applies to the entire aggregate, including all child entities.
|
|
282
|
+
*
|
|
283
|
+
* Provides core functionality:
|
|
284
|
+
* - ID and Version management (for Optimistic Concurrency Control)
|
|
285
|
+
* - State management (containing child entities and value objects)
|
|
286
|
+
* - Snapshot support for performance optimization
|
|
287
|
+
*
|
|
288
|
+
* Implements `AggregateRoot<TId>` to mark this as an Aggregate Root Entity.
|
|
289
|
+
*
|
|
290
|
+
* Use this class when you don't need Event Sourcing but still want
|
|
291
|
+
* aggregate patterns with versioning and state management.
|
|
292
|
+
*
|
|
293
|
+
* @template TState - The type of the aggregate state (contains child entities and value objects)
|
|
294
|
+
* @template TId - The type of the aggregate root identifier
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```typescript
|
|
298
|
+
* // Order is an Aggregate Root (an Entity)
|
|
299
|
+
* class Order extends AggregateBase<OrderState, OrderId> implements AggregateRoot<OrderId> {
|
|
300
|
+
* constructor(id: OrderId, initialState: OrderState) {
|
|
301
|
+
* super(id, initialState);
|
|
302
|
+
* }
|
|
303
|
+
*
|
|
304
|
+
* confirm(): void {
|
|
305
|
+
* this._state = { ...this._state, status: "confirmed" };
|
|
306
|
+
* this.bumpVersion(); // Versions the entire aggregate
|
|
307
|
+
* }
|
|
308
|
+
* }
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
declare abstract class AggregateBase<TState, TId extends Id<string>> implements AggregateRoot<TId> {
|
|
312
|
+
readonly id: TId;
|
|
313
|
+
version: Version;
|
|
314
|
+
private readonly _config;
|
|
315
|
+
private readonly _autoVersionBump;
|
|
316
|
+
get state(): TState;
|
|
317
|
+
/**
|
|
318
|
+
* The state is 'protected' so that only the subclass can change it.
|
|
319
|
+
* Subclasses can mutate this directly or use helper methods.
|
|
320
|
+
*/
|
|
321
|
+
protected _state: TState;
|
|
322
|
+
protected constructor(id: TId, initialState: TState, config?: AggregateConfig);
|
|
323
|
+
/**
|
|
324
|
+
* Manually bumps the aggregate version.
|
|
325
|
+
* Call this after state changes for Optimistic Concurrency Control.
|
|
326
|
+
*
|
|
327
|
+
* If `autoVersionBump` is enabled, this is called automatically
|
|
328
|
+
* when using `setState()`.
|
|
329
|
+
*/
|
|
330
|
+
protected bumpVersion(): void;
|
|
331
|
+
/**
|
|
332
|
+
* Sets the state and optionally bumps the version automatically.
|
|
333
|
+
* This is a convenience method for state mutations.
|
|
334
|
+
*
|
|
335
|
+
* @param newState - The new state
|
|
336
|
+
* @param bumpVersion - Whether to bump the version (defaults to autoVersionBump config)
|
|
337
|
+
*/
|
|
338
|
+
protected setState(newState: TState, bumpVersion?: boolean): void;
|
|
339
|
+
/**
|
|
340
|
+
* Creates a snapshot of the current aggregate state.
|
|
341
|
+
* Useful for performance optimization, backup/restore, and audit trails.
|
|
342
|
+
*
|
|
343
|
+
* @returns A snapshot containing the current state and version
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```typescript
|
|
347
|
+
* const snapshot = aggregate.createSnapshot();
|
|
348
|
+
* await snapshotRepository.save(aggregate.id, snapshot);
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
createSnapshot(): AggregateSnapshot<TState>;
|
|
352
|
+
/**
|
|
353
|
+
* Restores the aggregate from a snapshot.
|
|
354
|
+
* This is useful for loading aggregates from snapshots instead of
|
|
355
|
+
* rebuilding them from scratch.
|
|
356
|
+
*
|
|
357
|
+
* @param snapshot - The snapshot to restore from
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```typescript
|
|
361
|
+
* const snapshot = await snapshotRepository.getLatest(aggregateId);
|
|
362
|
+
* aggregate.restoreFromSnapshot(snapshot);
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
restoreFromSnapshot(snapshot: AggregateSnapshot<TState>): void;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
type Handler<TState, TEvent> = (state: TState, event: TEvent) => TState;
|
|
369
|
+
/**
|
|
370
|
+
* Extended configuration options for AggregateEventSourced behavior.
|
|
371
|
+
*/
|
|
372
|
+
interface AggregateEventSourcedConfig extends AggregateConfig {
|
|
373
|
+
/**
|
|
374
|
+
* Whether to automatically bump the version when applying new events.
|
|
375
|
+
* Defaults to true. Set to false to manually control versioning.
|
|
376
|
+
*/
|
|
377
|
+
autoVersionBump?: boolean;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Base class for Event-Sourced Aggregate Roots (Entities).
|
|
381
|
+
*
|
|
382
|
+
* Extends `AggregateBase` to create an Aggregate Root Entity with Event Sourcing capabilities.
|
|
383
|
+
* The Aggregate Root is the parent Entity of the aggregate and represents it externally.
|
|
384
|
+
*
|
|
385
|
+
* The aggregate state (`TState`) contains:
|
|
386
|
+
* - Child entities (Entities with id, but no own version)
|
|
387
|
+
* - Value objects (immutable objects)
|
|
388
|
+
*
|
|
389
|
+
* All changes to child entities are versioned through the Aggregate Root. The version
|
|
390
|
+
* applies to the entire aggregate, including all child entities.
|
|
391
|
+
*
|
|
392
|
+
* Extends `AggregateBase` with Event Sourcing capabilities:
|
|
393
|
+
* - Event tracking (pendingEvents)
|
|
394
|
+
* - Event handlers for state transitions
|
|
395
|
+
* - Event validation
|
|
396
|
+
* - History replay
|
|
397
|
+
*
|
|
398
|
+
* Use this class when you want Event Sourcing with full event tracking
|
|
399
|
+
* and replay capabilities.
|
|
400
|
+
*
|
|
401
|
+
* @template TState - The type of the aggregate state (contains child entities and value objects)
|
|
402
|
+
* @template TEvent - The union type of all domain events
|
|
403
|
+
* @template TId - The type of the aggregate root identifier
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* // Order is an Aggregate Root (an Entity) with Event Sourcing
|
|
408
|
+
* class Order extends AggregateEventSourced<OrderState, OrderEvent, OrderId> {
|
|
409
|
+
* confirm(): void {
|
|
410
|
+
* this.apply(createDomainEvent("OrderConfirmed", {}));
|
|
411
|
+
* }
|
|
412
|
+
*
|
|
413
|
+
* protected readonly handlers = {
|
|
414
|
+
* OrderConfirmed: (state: OrderState): OrderState => ({
|
|
415
|
+
* ...state,
|
|
416
|
+
* status: "confirmed",
|
|
417
|
+
* }),
|
|
418
|
+
* };
|
|
419
|
+
* }
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
declare abstract class AggregateEventSourced<TState, TEvent extends DomainEvent<string, unknown>, TId extends Id<string>> extends AggregateBase<TState, TId> {
|
|
423
|
+
private readonly _eventConfig;
|
|
424
|
+
private readonly _eventAutoVersionBump;
|
|
425
|
+
private readonly _pendingEvents;
|
|
426
|
+
protected constructor(id: TId, initialState: TState, config?: AggregateEventSourcedConfig);
|
|
427
|
+
/**
|
|
428
|
+
* Returns a read-only list of new, not-yet-persisted events.
|
|
429
|
+
*/
|
|
430
|
+
get pendingEvents(): ReadonlyArray<TEvent>;
|
|
431
|
+
/**
|
|
432
|
+
* Clears the list of pending events.
|
|
433
|
+
* Typically called after the events have been persisted.
|
|
434
|
+
*/
|
|
435
|
+
clearPendingEvents(): void;
|
|
436
|
+
/**
|
|
437
|
+
* Validates an event before it is applied.
|
|
438
|
+
* Override this method to add custom validation logic.
|
|
439
|
+
* Return `ok(true)` if the event is valid, `err(message)` otherwise.
|
|
440
|
+
*
|
|
441
|
+
* @param event - The event to validate
|
|
442
|
+
* @returns Result indicating if the event is valid
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* ```typescript
|
|
446
|
+
* protected validateEvent(event: OrderEvent): Result<true, string> {
|
|
447
|
+
* if (event.type === "OrderShipped" && this.state.status !== "confirmed") {
|
|
448
|
+
* return err("Order must be confirmed before shipping");
|
|
449
|
+
* }
|
|
450
|
+
* return ok(true);
|
|
451
|
+
* }
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
protected validateEvent(_event: TEvent): Result<true, string>;
|
|
455
|
+
/**
|
|
456
|
+
* Applies an event to change the state and adds it
|
|
457
|
+
* to the list of pending events.
|
|
458
|
+
* Returns a Result type instead of throwing an error.
|
|
459
|
+
*
|
|
460
|
+
* @param event - The domain event to apply
|
|
461
|
+
* @param isNew - Indicates whether the event is new (and needs to be persisted)
|
|
462
|
+
* or if it is being loaded from history
|
|
463
|
+
* @returns Result<void, string> - ok if successful, err with error message if validation fails or handler is missing
|
|
464
|
+
*/
|
|
465
|
+
protected apply(event: TEvent, isNew?: boolean): Result<void, string>;
|
|
466
|
+
/**
|
|
467
|
+
* Applies an event to change the state and adds it
|
|
468
|
+
* to the list of pending events.
|
|
469
|
+
* Throws an error if validation fails or handler is missing.
|
|
470
|
+
*
|
|
471
|
+
* @param event - The domain event to apply
|
|
472
|
+
* @param isNew - Indicates whether the event is new (and needs to be persisted)
|
|
473
|
+
* or if it is being loaded from history
|
|
474
|
+
* @throws Error if event validation fails or handler is missing
|
|
475
|
+
*/
|
|
476
|
+
protected applyUnsafe(event: TEvent, isNew?: boolean): void;
|
|
477
|
+
/**
|
|
478
|
+
* Manually bumps the aggregate version.
|
|
479
|
+
* Only needed if `autoVersionBump` is disabled.
|
|
480
|
+
*/
|
|
481
|
+
protected bumpVersion(): void;
|
|
482
|
+
/**
|
|
483
|
+
* Reconstitutes the aggregate from an event history.
|
|
484
|
+
* Sets the version to the number of events in the history.
|
|
485
|
+
*
|
|
486
|
+
* @param history - An ordered list of past events
|
|
487
|
+
*/
|
|
488
|
+
loadFromHistory(history: TEvent[]): Result<void, string>;
|
|
489
|
+
/**
|
|
490
|
+
* Checks if the aggregate has any pending events.
|
|
491
|
+
*
|
|
492
|
+
* @returns true if there are pending events, false otherwise
|
|
493
|
+
*/
|
|
494
|
+
hasPendingEvents(): boolean;
|
|
495
|
+
/**
|
|
496
|
+
* Returns the number of pending events.
|
|
497
|
+
*
|
|
498
|
+
* @returns The count of pending events
|
|
499
|
+
*/
|
|
500
|
+
getEventCount(): number;
|
|
501
|
+
/**
|
|
502
|
+
* Returns the latest pending event, if any.
|
|
503
|
+
*
|
|
504
|
+
* @returns The most recent event or undefined if no events exist
|
|
505
|
+
*/
|
|
506
|
+
getLatestEvent(): TEvent | undefined;
|
|
507
|
+
/**
|
|
508
|
+
* Restores the aggregate from a snapshot and applies events that occurred after the snapshot.
|
|
509
|
+
* This is more efficient than replaying all events from the beginning.
|
|
510
|
+
*
|
|
511
|
+
* @param snapshot - The snapshot to restore from
|
|
512
|
+
* @param eventsAfterSnapshot - Events that occurred after the snapshot was taken
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```typescript
|
|
516
|
+
* const snapshot = await snapshotRepository.getLatest(aggregateId);
|
|
517
|
+
* const eventsAfter = await eventStore.getEventsAfter(aggregateId, snapshot.version);
|
|
518
|
+
* aggregate.restoreFromSnapshotWithEvents(snapshot, eventsAfter);
|
|
519
|
+
* ```
|
|
520
|
+
*/
|
|
521
|
+
restoreFromSnapshotWithEvents(snapshot: AggregateSnapshot<TState>, eventsAfterSnapshot: TEvent[]): Result<void, string>;
|
|
522
|
+
/**
|
|
523
|
+
* A map of event types to their corresponding handlers.
|
|
524
|
+
* Subclasses MUST implement this property.
|
|
525
|
+
*/
|
|
526
|
+
protected abstract readonly handlers: {
|
|
527
|
+
[K in TEvent["type"]]: Handler<TState, Extract<TEvent, {
|
|
528
|
+
type: K;
|
|
529
|
+
}>>;
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Marker interface for Commands.
|
|
535
|
+
* Commands represent write operations that change system state.
|
|
536
|
+
* They should be immutable and contain all data needed to perform the operation.
|
|
537
|
+
*
|
|
538
|
+
* This interface can be used as a type marker even when using external frameworks
|
|
539
|
+
* (e.g., RabbitMQ, AWS SQS) to ensure type safety across different bus implementations.
|
|
540
|
+
*
|
|
541
|
+
* @example
|
|
542
|
+
* ```typescript
|
|
543
|
+
* type CreateOrderCommand = Command & {
|
|
544
|
+
* type: "CreateOrder";
|
|
545
|
+
* customerId: string;
|
|
546
|
+
* items: OrderItem[];
|
|
547
|
+
* };
|
|
548
|
+
* ```
|
|
549
|
+
*
|
|
550
|
+
* @example Using with external frameworks (RabbitMQ, etc.)
|
|
551
|
+
* ```typescript
|
|
552
|
+
* // Define command using Command marker
|
|
553
|
+
* type CreateOrderCommand = Command & {
|
|
554
|
+
* type: "CreateOrder";
|
|
555
|
+
* customerId: string;
|
|
556
|
+
* };
|
|
557
|
+
*
|
|
558
|
+
* // Handler can be typed with CommandHandler even for external frameworks
|
|
559
|
+
* const handler: CommandHandler<CreateOrderCommand, OrderId> = async (cmd) => {
|
|
560
|
+
* // ... handler logic
|
|
561
|
+
* return ok(orderId);
|
|
562
|
+
* };
|
|
563
|
+
*
|
|
564
|
+
* // Register with RabbitMQ or other external bus
|
|
565
|
+
* rabbitMQChannel.consume("order.commands", async (message) => {
|
|
566
|
+
* const command = JSON.parse(message.content) as CreateOrderCommand;
|
|
567
|
+
* const result = await handler(command);
|
|
568
|
+
* // ... handle result
|
|
569
|
+
* });
|
|
570
|
+
* ```
|
|
571
|
+
*/
|
|
572
|
+
interface Command {
|
|
573
|
+
readonly type: string;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Handler for executing commands.
|
|
577
|
+
* Commands return Result for explicit error handling.
|
|
578
|
+
* Commands may modify system state and should be idempotent when possible.
|
|
579
|
+
*
|
|
580
|
+
* This type can be used to mark handlers even when using external frameworks
|
|
581
|
+
* (e.g., RabbitMQ, AWS SQS, Kafka) to ensure type safety and consistency.
|
|
582
|
+
*
|
|
583
|
+
* @template C - The command type (must extend Command)
|
|
584
|
+
* @template R - The result type
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* ```typescript
|
|
588
|
+
* const handler: CommandHandler<CreateOrderCommand, OrderId> = async (cmd) => {
|
|
589
|
+
* const order = Order.create(cmd.customerId, cmd.items);
|
|
590
|
+
* await repository.save(order);
|
|
591
|
+
* return ok(order.id);
|
|
592
|
+
* };
|
|
593
|
+
* ```
|
|
594
|
+
*
|
|
595
|
+
* @example Using with external frameworks
|
|
596
|
+
* ```typescript
|
|
597
|
+
* // Handler typed with CommandHandler for type safety
|
|
598
|
+
* const createOrderHandler: CommandHandler<CreateOrderCommand, OrderId> = async (cmd) => {
|
|
599
|
+
* // ... handler logic
|
|
600
|
+
* return ok(orderId);
|
|
601
|
+
* };
|
|
602
|
+
*
|
|
603
|
+
* // Can be used with any external bus/framework
|
|
604
|
+
* // RabbitMQ example:
|
|
605
|
+
* rabbitMQChannel.consume("commands", async (msg) => {
|
|
606
|
+
* const command = JSON.parse(msg.content) as CreateOrderCommand;
|
|
607
|
+
* const result = await createOrderHandler(command);
|
|
608
|
+
* // ... handle result
|
|
609
|
+
* });
|
|
610
|
+
*
|
|
611
|
+
* // AWS SQS example:
|
|
612
|
+
* sqs.receiveMessage({ QueueUrl: "..." }, async (err, data) => {
|
|
613
|
+
* const command = JSON.parse(data.Messages[0].Body) as CreateOrderCommand;
|
|
614
|
+
* const result = await createOrderHandler(command);
|
|
615
|
+
* // ... handle result
|
|
616
|
+
* });
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
619
|
+
type CommandHandler<C extends Command, R> = (cmd: C) => Promise<Result<R, string>>;
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Command Bus interface for dispatching commands to their handlers.
|
|
623
|
+
* Provides a centralized way to execute commands with handler registration.
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```typescript
|
|
627
|
+
* const bus = new CommandBus();
|
|
628
|
+
* bus.register("CreateOrder", createOrderHandler);
|
|
629
|
+
*
|
|
630
|
+
* const result = await bus.execute({
|
|
631
|
+
* type: "CreateOrder",
|
|
632
|
+
* customerId: "123",
|
|
633
|
+
* items: [...]
|
|
634
|
+
* });
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
interface ICommandBus {
|
|
638
|
+
/**
|
|
639
|
+
* Executes a command by dispatching it to the registered handler.
|
|
640
|
+
*
|
|
641
|
+
* @param command - The command to execute
|
|
642
|
+
* @returns Result containing the success value or error message
|
|
643
|
+
*/
|
|
644
|
+
execute<C extends Command, R>(command: C): Promise<Result<R, string>>;
|
|
645
|
+
/**
|
|
646
|
+
* Registers a handler for a specific command type.
|
|
647
|
+
*
|
|
648
|
+
* @param commandType - The command type to register the handler for
|
|
649
|
+
* @param handler - The handler function for this command type
|
|
650
|
+
*/
|
|
651
|
+
register<C extends Command, R>(commandType: C["type"], handler: CommandHandler<C, R>): void;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Simple in-memory command bus implementation.
|
|
655
|
+
* Handlers are stored in a Map and dispatched based on command type.
|
|
656
|
+
*
|
|
657
|
+
* **Note:** This is a basic implementation suitable for development and simple use cases.
|
|
658
|
+
* For production environments, consider implementing or using a more feature-rich bus that includes:
|
|
659
|
+
* - Middleware/Pipeline support (logging, validation, authorization)
|
|
660
|
+
* - Error handling and retry logic
|
|
661
|
+
* - Timeout handling
|
|
662
|
+
* - Metrics and observability
|
|
663
|
+
* - Transaction management
|
|
664
|
+
* - Dead letter queue support
|
|
665
|
+
*
|
|
666
|
+
* The `CommandHandler` type can still be used with external production-grade buses
|
|
667
|
+
* (e.g., RabbitMQ, AWS SQS) while maintaining type safety.
|
|
668
|
+
*
|
|
669
|
+
* @example
|
|
670
|
+
* ```typescript
|
|
671
|
+
* const bus = new CommandBus();
|
|
672
|
+
* bus.register("CreateOrder", async (cmd) => {
|
|
673
|
+
* // ... handler logic
|
|
674
|
+
* return ok(orderId);
|
|
675
|
+
* });
|
|
676
|
+
*
|
|
677
|
+
* const result = await bus.execute({ type: "CreateOrder", ... });
|
|
678
|
+
* ```
|
|
679
|
+
*/
|
|
680
|
+
declare class CommandBus implements ICommandBus {
|
|
681
|
+
private readonly handlers;
|
|
682
|
+
register<C extends Command, R>(commandType: C["type"], handler: CommandHandler<C, R>): void;
|
|
683
|
+
execute<C extends Command, R>(command: C): Promise<Result<R, string>>;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Event handler function type for subscribing to domain events.
|
|
688
|
+
*
|
|
689
|
+
* @template Evt - The type of domain event
|
|
690
|
+
*/
|
|
691
|
+
type EventHandler<Evt> = (event: Evt) => Promise<void> | void;
|
|
692
|
+
/**
|
|
693
|
+
* Event Bus interface for publishing and subscribing to domain events.
|
|
694
|
+
* Supports multiple subscribers per event type (pub/sub pattern).
|
|
695
|
+
*
|
|
696
|
+
* @template Evt - The type of domain events
|
|
697
|
+
*
|
|
698
|
+
* @example
|
|
699
|
+
* ```typescript
|
|
700
|
+
* const bus = new EventBus<OrderEvent>();
|
|
701
|
+
*
|
|
702
|
+
* // Subscribe to specific event types
|
|
703
|
+
* bus.subscribe("OrderCreated", async (event) => {
|
|
704
|
+
* await sendEmail(event.payload.customerId);
|
|
705
|
+
* });
|
|
706
|
+
*
|
|
707
|
+
* bus.subscribe("OrderShipped", async (event) => {
|
|
708
|
+
* await updateInventory(event.payload.orderId);
|
|
709
|
+
* });
|
|
710
|
+
*
|
|
711
|
+
* // Publish events
|
|
712
|
+
* await bus.publish([orderCreatedEvent, orderShippedEvent]);
|
|
713
|
+
* ```
|
|
714
|
+
*/
|
|
715
|
+
interface EventBus<Evt> {
|
|
716
|
+
/**
|
|
717
|
+
* Publishes events to all subscribed handlers.
|
|
718
|
+
* All handlers for each event type will be called.
|
|
719
|
+
*
|
|
720
|
+
* @param events - Array of events to publish
|
|
721
|
+
*/
|
|
722
|
+
publish: (events: ReadonlyArray<Evt>) => Promise<void>;
|
|
723
|
+
/**
|
|
724
|
+
* Subscribes a handler to a specific event type.
|
|
725
|
+
* Multiple handlers can subscribe to the same event type.
|
|
726
|
+
*
|
|
727
|
+
* @param eventType - The event type to subscribe to
|
|
728
|
+
* @param handler - The handler function to call when events of this type are published
|
|
729
|
+
* @returns A function to unsubscribe the handler
|
|
730
|
+
*
|
|
731
|
+
* @example
|
|
732
|
+
* ```typescript
|
|
733
|
+
* const unsubscribe = bus.subscribe("OrderCreated", async (event) => {
|
|
734
|
+
* console.log("Order created:", event.payload.orderId);
|
|
735
|
+
* });
|
|
736
|
+
*
|
|
737
|
+
* // Later: unsubscribe
|
|
738
|
+
* unsubscribe();
|
|
739
|
+
* ```
|
|
740
|
+
*/
|
|
741
|
+
subscribe: <T extends Evt>(eventType: string, handler: EventHandler<T>) => () => void;
|
|
742
|
+
}
|
|
743
|
+
interface Outbox<Evt> {
|
|
744
|
+
add: (events: ReadonlyArray<Evt>) => Promise<void>;
|
|
745
|
+
}
|
|
746
|
+
interface Clock {
|
|
747
|
+
now: () => Date;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
interface UnitOfWork {
|
|
751
|
+
transactional<T>(fn: () => Promise<T>): Promise<T>;
|
|
752
|
+
}
|
|
753
|
+
type RepoProvider<R> = (uow: UnitOfWork) => R;
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Helper function for executing commands within a transaction.
|
|
757
|
+
* Handles event persistence via outbox and optional event bus publishing.
|
|
758
|
+
*
|
|
759
|
+
* @param deps - Dependencies including outbox, optional event bus, and unit of work
|
|
760
|
+
* @param fn - Function that returns result and events
|
|
761
|
+
* @returns The result wrapped in a transaction
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* ```typescript
|
|
765
|
+
* const result = await withCommit(
|
|
766
|
+
* { outbox, bus, uow },
|
|
767
|
+
* async () => {
|
|
768
|
+
* const order = Order.create(customerId, items);
|
|
769
|
+
* await repository.save(order);
|
|
770
|
+
* return {
|
|
771
|
+
* result: order.id,
|
|
772
|
+
* events: order.pendingEvents
|
|
773
|
+
* };
|
|
774
|
+
* }
|
|
775
|
+
* );
|
|
776
|
+
* ```
|
|
777
|
+
*/
|
|
778
|
+
declare function withCommit<Evt, R>(deps: {
|
|
779
|
+
outbox: Outbox<Evt>;
|
|
780
|
+
bus?: EventBus<Evt>;
|
|
781
|
+
uow: UnitOfWork;
|
|
782
|
+
}, fn: () => Promise<{
|
|
783
|
+
result: R;
|
|
784
|
+
events: ReadonlyArray<Evt>;
|
|
785
|
+
}>): Promise<R>;
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Marker interface for Queries.
|
|
789
|
+
* Queries represent read operations that don't change system state.
|
|
790
|
+
* They should be immutable and contain all data needed to perform the read.
|
|
791
|
+
*
|
|
792
|
+
* This interface can be used as a type marker even when using external frameworks
|
|
793
|
+
* (e.g., RabbitMQ, AWS SQS) to ensure type safety across different bus implementations.
|
|
794
|
+
*
|
|
795
|
+
* @example
|
|
796
|
+
* ```typescript
|
|
797
|
+
* type GetOrderQuery = Query & {
|
|
798
|
+
* type: "GetOrder";
|
|
799
|
+
* orderId: OrderId;
|
|
800
|
+
* };
|
|
801
|
+
* ```
|
|
802
|
+
*
|
|
803
|
+
* @example Using with external frameworks
|
|
804
|
+
* ```typescript
|
|
805
|
+
* type GetOrderQuery = Query & {
|
|
806
|
+
* type: "GetOrder";
|
|
807
|
+
* orderId: OrderId;
|
|
808
|
+
* };
|
|
809
|
+
*
|
|
810
|
+
* // Handler typed with QueryHandler for type safety
|
|
811
|
+
* const handler: QueryHandler<GetOrderQuery, Order | null> = async (query) => {
|
|
812
|
+
* return await repository.getById(query.orderId);
|
|
813
|
+
* };
|
|
814
|
+
*
|
|
815
|
+
* // Can be used with any external framework
|
|
816
|
+
* rabbitMQChannel.consume("queries", async (message) => {
|
|
817
|
+
* const query = JSON.parse(message.content) as GetOrderQuery;
|
|
818
|
+
* const result = await handler(query);
|
|
819
|
+
* // ... handle result
|
|
820
|
+
* });
|
|
821
|
+
* ```
|
|
822
|
+
*/
|
|
823
|
+
interface Query {
|
|
824
|
+
readonly type: string;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Handler for executing queries.
|
|
828
|
+
* Queries return data directly (no Result type) as read operations
|
|
829
|
+
* are not expected to fail in normal circumstances.
|
|
830
|
+
* Queries should not modify system state and can be cached.
|
|
831
|
+
*
|
|
832
|
+
* This type can be used to mark handlers even when using external frameworks
|
|
833
|
+
* (e.g., RabbitMQ, AWS SQS, Kafka) to ensure type safety and consistency.
|
|
834
|
+
*
|
|
835
|
+
* @template Q - The query type (must extend Query)
|
|
836
|
+
* @template R - The result type
|
|
837
|
+
*
|
|
838
|
+
* @example
|
|
839
|
+
* ```typescript
|
|
840
|
+
* const handler: QueryHandler<GetOrderQuery, Order | null> = async (query) => {
|
|
841
|
+
* return await repository.getById(query.orderId);
|
|
842
|
+
* };
|
|
843
|
+
* ```
|
|
844
|
+
*
|
|
845
|
+
* @example Using with external frameworks
|
|
846
|
+
* ```typescript
|
|
847
|
+
* // Handler typed with QueryHandler for type safety
|
|
848
|
+
* const getOrderHandler: QueryHandler<GetOrderQuery, Order | null> = async (query) => {
|
|
849
|
+
* return await repository.getById(query.orderId);
|
|
850
|
+
* };
|
|
851
|
+
*
|
|
852
|
+
* // Can be used with any external bus/framework
|
|
853
|
+
* rabbitMQChannel.consume("queries", async (msg) => {
|
|
854
|
+
* const query = JSON.parse(msg.content) as GetOrderQuery;
|
|
855
|
+
* const result = await getOrderHandler(query);
|
|
856
|
+
* // ... handle result
|
|
857
|
+
* });
|
|
858
|
+
* ```
|
|
859
|
+
*/
|
|
860
|
+
type QueryHandler<Q extends Query, R> = (query: Q) => Promise<R>;
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Query Bus interface for dispatching queries to their handlers.
|
|
864
|
+
* Provides a centralized way to execute queries with handler registration.
|
|
865
|
+
*
|
|
866
|
+
* @example
|
|
867
|
+
* ```typescript
|
|
868
|
+
* const bus = new QueryBus();
|
|
869
|
+
* bus.register("GetOrder", getOrderHandler);
|
|
870
|
+
*
|
|
871
|
+
* const order = await bus.execute({
|
|
872
|
+
* type: "GetOrder",
|
|
873
|
+
* orderId: "123"
|
|
874
|
+
* });
|
|
875
|
+
* ```
|
|
876
|
+
*/
|
|
877
|
+
interface IQueryBus {
|
|
878
|
+
/**
|
|
879
|
+
* Executes a query by dispatching it to the registered handler.
|
|
880
|
+
* Returns a Result type instead of throwing an error.
|
|
881
|
+
*
|
|
882
|
+
* @param query - The query to execute
|
|
883
|
+
* @returns Result containing the query result if successful, or an error message if no handler is registered
|
|
884
|
+
*/
|
|
885
|
+
execute<Q extends Query, R>(query: Q): Promise<Result<R, string>>;
|
|
886
|
+
/**
|
|
887
|
+
* Executes a query by dispatching it to the registered handler.
|
|
888
|
+
* Throws an error if no handler is registered.
|
|
889
|
+
*
|
|
890
|
+
* @param query - The query to execute
|
|
891
|
+
* @returns The query result
|
|
892
|
+
* @throws Error if no handler is registered for the query type
|
|
893
|
+
*/
|
|
894
|
+
executeUnsafe<Q extends Query, R>(query: Q): Promise<R>;
|
|
895
|
+
/**
|
|
896
|
+
* Registers a handler for a specific query type.
|
|
897
|
+
*
|
|
898
|
+
* @param queryType - The query type to register the handler for
|
|
899
|
+
* @param handler - The handler function for this query type
|
|
900
|
+
*/
|
|
901
|
+
register<Q extends Query, R>(queryType: Q["type"], handler: QueryHandler<Q, R>): void;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Type map for query types to their return types.
|
|
905
|
+
* Used to improve type inference in QueryBus.
|
|
906
|
+
*/
|
|
907
|
+
type QueryTypeMap = Record<string, unknown>;
|
|
908
|
+
/**
|
|
909
|
+
* Simple in-memory query bus implementation.
|
|
910
|
+
* Handlers are stored in a Map and dispatched based on query type.
|
|
911
|
+
*
|
|
912
|
+
* **Note:** This is a basic implementation suitable for development and simple use cases.
|
|
913
|
+
* For production environments, consider implementing or using a more feature-rich bus that includes:
|
|
914
|
+
* - Middleware/Pipeline support (logging, caching, rate limiting)
|
|
915
|
+
* - Error handling
|
|
916
|
+
* - Timeout handling
|
|
917
|
+
* - Metrics and observability
|
|
918
|
+
* - Query result caching
|
|
919
|
+
* - Rate limiting
|
|
920
|
+
*
|
|
921
|
+
* The `QueryHandler` type can still be used with external production-grade buses
|
|
922
|
+
* (e.g., RabbitMQ, AWS SQS) while maintaining type safety.
|
|
923
|
+
*
|
|
924
|
+
* @example
|
|
925
|
+
* ```typescript
|
|
926
|
+
* const bus = new QueryBus();
|
|
927
|
+
* bus.register("GetOrder", async (query) => {
|
|
928
|
+
* return await repository.getById(query.orderId);
|
|
929
|
+
* });
|
|
930
|
+
*
|
|
931
|
+
* const order = await bus.execute({ type: "GetOrder", orderId: "123" });
|
|
932
|
+
* ```
|
|
933
|
+
*/
|
|
934
|
+
declare class QueryBus<TMap extends QueryTypeMap = QueryTypeMap> implements IQueryBus {
|
|
935
|
+
private readonly handlers;
|
|
936
|
+
register<Q extends Query, R>(queryType: Q["type"], handler: QueryHandler<Q, R>): void;
|
|
937
|
+
execute<Q extends Query & {
|
|
938
|
+
type: keyof TMap;
|
|
939
|
+
}>(query: Q): Promise<Result<TMap[Q["type"]], string>>;
|
|
940
|
+
execute<Q extends Query, R>(query: Q): Promise<Result<R, string>>;
|
|
941
|
+
executeUnsafe<Q extends Query, R>(query: Q): Promise<R>;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Guard function that validates a condition and returns a Result.
|
|
946
|
+
* Returns `ok(true)` if the condition is met, otherwise `err(error)`.
|
|
947
|
+
*
|
|
948
|
+
* @param cond - The condition to check
|
|
949
|
+
* @param error - Error message if condition fails
|
|
950
|
+
* @returns Result<true, string>
|
|
951
|
+
*
|
|
952
|
+
* @example
|
|
953
|
+
* ```typescript
|
|
954
|
+
* const result = guard(id.length > 0, "ID cannot be empty");
|
|
955
|
+
* if (!result.ok) {
|
|
956
|
+
* return err(result.error);
|
|
957
|
+
* }
|
|
958
|
+
* ```
|
|
959
|
+
*/
|
|
960
|
+
declare function guard(cond: boolean, error: string): Result<true, string>;
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Interface for entities with identity.
|
|
964
|
+
*
|
|
965
|
+
* In Domain-Driven Design, there are two types of entities:
|
|
966
|
+
*
|
|
967
|
+
* 1. **Aggregate Root Entity**: The parent Entity of an aggregate.
|
|
968
|
+
* - Has identity (id) and version
|
|
969
|
+
* - Implemented by classes extending `AggregateBase` or `AggregateEventSourced`
|
|
970
|
+
* - Represents the aggregate externally
|
|
971
|
+
* - Loaded/saved through repositories
|
|
972
|
+
*
|
|
973
|
+
* 2. **Child Entities**: Entities within an aggregate.
|
|
974
|
+
* - Have identity (id), but no own version
|
|
975
|
+
* - Exist only within the aggregate boundary
|
|
976
|
+
* - Versioned through the Aggregate Root
|
|
977
|
+
* - Cannot be referenced directly from outside the aggregate
|
|
978
|
+
*
|
|
979
|
+
* This interface is used for child entities within aggregates. The Aggregate Root
|
|
980
|
+
* also implements this interface (through `AggregateRoot<TId>`), but additionally
|
|
981
|
+
* has version management.
|
|
982
|
+
*
|
|
983
|
+
* @template TId - The type of the entity identifier
|
|
984
|
+
*
|
|
985
|
+
* @example
|
|
986
|
+
* ```typescript
|
|
987
|
+
* // Child Entity within an aggregate
|
|
988
|
+
* type OrderItem = Entity<ItemId> & {
|
|
989
|
+
* productId: string;
|
|
990
|
+
* quantity: number;
|
|
991
|
+
* };
|
|
992
|
+
*
|
|
993
|
+
* // Aggregate Root (also an Entity, but with version)
|
|
994
|
+
* class Order extends AggregateBase<OrderState, OrderId>
|
|
995
|
+
* implements AggregateRoot<OrderId> {
|
|
996
|
+
* // Order is an Entity (the Aggregate Root)
|
|
997
|
+
* // OrderState contains OrderItem (child entities)
|
|
998
|
+
* }
|
|
999
|
+
* ```
|
|
1000
|
+
*/
|
|
1001
|
+
interface Entity<TId> {
|
|
1002
|
+
readonly id: TId;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Checks if two entities have the same ID.
|
|
1006
|
+
* Works with any object that has an 'id' property.
|
|
1007
|
+
*
|
|
1008
|
+
* @param a - First entity
|
|
1009
|
+
* @param b - Second entity
|
|
1010
|
+
* @returns true if both entities have the same ID, false otherwise
|
|
1011
|
+
*
|
|
1012
|
+
* @example
|
|
1013
|
+
* ```typescript
|
|
1014
|
+
* const item1: OrderItem = { id: itemId1, productId: "prod-1", quantity: 2 };
|
|
1015
|
+
* const item2: OrderItem = { id: itemId2, productId: "prod-2", quantity: 1 };
|
|
1016
|
+
*
|
|
1017
|
+
* sameEntity(item1, item2); // false
|
|
1018
|
+
* sameEntity(item1, item1); // true
|
|
1019
|
+
* ```
|
|
1020
|
+
*/
|
|
1021
|
+
declare function sameEntity<TId>(a: {
|
|
1022
|
+
id: TId;
|
|
1023
|
+
}, b: {
|
|
1024
|
+
id: TId;
|
|
1025
|
+
}): boolean;
|
|
1026
|
+
/**
|
|
1027
|
+
* Finds an entity by ID in a collection.
|
|
1028
|
+
* Returns undefined if not found.
|
|
1029
|
+
*
|
|
1030
|
+
* @param entities - Array of entities to search
|
|
1031
|
+
* @param id - The ID to search for
|
|
1032
|
+
* @returns The entity if found, undefined otherwise
|
|
1033
|
+
*
|
|
1034
|
+
* @example
|
|
1035
|
+
* ```typescript
|
|
1036
|
+
* const items: OrderItem[] = [
|
|
1037
|
+
* { id: itemId1, productId: "prod-1", quantity: 2 },
|
|
1038
|
+
* { id: itemId2, productId: "prod-2", quantity: 1 }
|
|
1039
|
+
* ];
|
|
1040
|
+
*
|
|
1041
|
+
* const item = findEntityById(items, itemId1);
|
|
1042
|
+
* // item is { id: itemId1, productId: "prod-1", quantity: 2 }
|
|
1043
|
+
* ```
|
|
1044
|
+
*/
|
|
1045
|
+
declare function findEntityById<TId, T extends {
|
|
1046
|
+
id: TId;
|
|
1047
|
+
}>(entities: T[], id: TId): T | undefined;
|
|
1048
|
+
/**
|
|
1049
|
+
* Checks if an entity with the given ID exists in the collection.
|
|
1050
|
+
*
|
|
1051
|
+
* @param entities - Array of entities to search
|
|
1052
|
+
* @param id - The ID to check for
|
|
1053
|
+
* @returns true if an entity with the ID exists, false otherwise
|
|
1054
|
+
*
|
|
1055
|
+
* @example
|
|
1056
|
+
* ```typescript
|
|
1057
|
+
* const items: OrderItem[] = [
|
|
1058
|
+
* { id: itemId1, productId: "prod-1", quantity: 2 }
|
|
1059
|
+
* ];
|
|
1060
|
+
*
|
|
1061
|
+
* hasEntityId(items, itemId1); // true
|
|
1062
|
+
* hasEntityId(items, itemId2); // false
|
|
1063
|
+
* ```
|
|
1064
|
+
*/
|
|
1065
|
+
declare function hasEntityId<TId, T extends {
|
|
1066
|
+
id: TId;
|
|
1067
|
+
}>(entities: T[], id: TId): boolean;
|
|
1068
|
+
/**
|
|
1069
|
+
* Removes an entity with the given ID from the collection.
|
|
1070
|
+
* Returns a new array without the entity.
|
|
1071
|
+
*
|
|
1072
|
+
* @param entities - Array of entities
|
|
1073
|
+
* @param id - The ID of the entity to remove
|
|
1074
|
+
* @returns A new array without the entity with the given ID
|
|
1075
|
+
*
|
|
1076
|
+
* @example
|
|
1077
|
+
* ```typescript
|
|
1078
|
+
* const items: OrderItem[] = [
|
|
1079
|
+
* { id: itemId1, productId: "prod-1", quantity: 2 },
|
|
1080
|
+
* { id: itemId2, productId: "prod-2", quantity: 1 }
|
|
1081
|
+
* ];
|
|
1082
|
+
*
|
|
1083
|
+
* const updated = removeEntityById(items, itemId1);
|
|
1084
|
+
* // updated is [{ id: itemId2, productId: "prod-2", quantity: 1 }]
|
|
1085
|
+
* ```
|
|
1086
|
+
*/
|
|
1087
|
+
declare function removeEntityById<TId, T extends {
|
|
1088
|
+
id: TId;
|
|
1089
|
+
}>(entities: T[], id: TId): T[];
|
|
1090
|
+
/**
|
|
1091
|
+
* Updates an entity with the given ID in the collection.
|
|
1092
|
+
* Returns a new array with the updated entity.
|
|
1093
|
+
* If the entity is not found, returns the original array unchanged.
|
|
1094
|
+
*
|
|
1095
|
+
* @param entities - Array of entities
|
|
1096
|
+
* @param id - The ID of the entity to update
|
|
1097
|
+
* @param updater - Function that takes the entity and returns the updated entity
|
|
1098
|
+
* @returns A new array with the updated entity
|
|
1099
|
+
*
|
|
1100
|
+
* @example
|
|
1101
|
+
* ```typescript
|
|
1102
|
+
* const items: OrderItem[] = [
|
|
1103
|
+
* { id: itemId1, productId: "prod-1", quantity: 2 }
|
|
1104
|
+
* ];
|
|
1105
|
+
*
|
|
1106
|
+
* const updated = updateEntityById(items, itemId1, (item) => ({
|
|
1107
|
+
* ...item,
|
|
1108
|
+
* quantity: item.quantity + 1
|
|
1109
|
+
* }));
|
|
1110
|
+
* // updated is [{ id: itemId1, productId: "prod-1", quantity: 3 }]
|
|
1111
|
+
* ```
|
|
1112
|
+
*/
|
|
1113
|
+
declare function updateEntityById<TId, T extends {
|
|
1114
|
+
id: TId;
|
|
1115
|
+
}>(entities: T[], id: TId, updater: (entity: T) => T): T[];
|
|
1116
|
+
/**
|
|
1117
|
+
* Replaces an entity with the given ID in the collection.
|
|
1118
|
+
* Returns a new array with the replaced entity.
|
|
1119
|
+
* If the entity is not found, returns the original array unchanged.
|
|
1120
|
+
*
|
|
1121
|
+
* @param entities - Array of entities
|
|
1122
|
+
* @param id - The ID of the entity to replace
|
|
1123
|
+
* @param replacement - The replacement entity
|
|
1124
|
+
* @returns A new array with the replaced entity
|
|
1125
|
+
*
|
|
1126
|
+
* @example
|
|
1127
|
+
* ```typescript
|
|
1128
|
+
* const items: OrderItem[] = [
|
|
1129
|
+
* { id: itemId1, productId: "prod-1", quantity: 2 }
|
|
1130
|
+
* ];
|
|
1131
|
+
*
|
|
1132
|
+
* const updated = replaceEntityById(items, itemId1, {
|
|
1133
|
+
* id: itemId1,
|
|
1134
|
+
* productId: "prod-1",
|
|
1135
|
+
* quantity: 5
|
|
1136
|
+
* });
|
|
1137
|
+
* ```
|
|
1138
|
+
*/
|
|
1139
|
+
declare function replaceEntityById<TId, T extends {
|
|
1140
|
+
id: TId;
|
|
1141
|
+
}>(entities: T[], id: TId, replacement: T): T[];
|
|
1142
|
+
/**
|
|
1143
|
+
* Extracts all IDs from a collection of entities.
|
|
1144
|
+
*
|
|
1145
|
+
* @param entities - Array of entities
|
|
1146
|
+
* @returns Array of entity IDs
|
|
1147
|
+
*
|
|
1148
|
+
* @example
|
|
1149
|
+
* ```typescript
|
|
1150
|
+
* const items: OrderItem[] = [
|
|
1151
|
+
* { id: itemId1, productId: "prod-1", quantity: 2 },
|
|
1152
|
+
* { id: itemId2, productId: "prod-2", quantity: 1 }
|
|
1153
|
+
* ];
|
|
1154
|
+
*
|
|
1155
|
+
* const ids = entityIds(items);
|
|
1156
|
+
* // ids is [itemId1, itemId2]
|
|
1157
|
+
* ```
|
|
1158
|
+
*/
|
|
1159
|
+
declare function entityIds<TId, T extends {
|
|
1160
|
+
id: TId;
|
|
1161
|
+
}>(entities: T[]): TId[];
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Simple in-memory event bus implementation.
|
|
1165
|
+
* Supports multiple subscribers per event type (pub/sub pattern).
|
|
1166
|
+
*
|
|
1167
|
+
* @template Evt - The type of domain events (must extend DomainEvent)
|
|
1168
|
+
*
|
|
1169
|
+
* @example
|
|
1170
|
+
* ```typescript
|
|
1171
|
+
* const bus = new EventBusImpl<OrderEvent>();
|
|
1172
|
+
*
|
|
1173
|
+
* bus.subscribe("OrderCreated", async (event) => {
|
|
1174
|
+
* await sendEmail(event.payload.customerId);
|
|
1175
|
+
* });
|
|
1176
|
+
*
|
|
1177
|
+
* bus.subscribe("OrderCreated", async (event) => {
|
|
1178
|
+
* await logEvent(event);
|
|
1179
|
+
* });
|
|
1180
|
+
*
|
|
1181
|
+
* await bus.publish([orderCreatedEvent]);
|
|
1182
|
+
* // Both handlers will be called
|
|
1183
|
+
* ```
|
|
1184
|
+
*/
|
|
1185
|
+
declare class EventBusImpl<Evt extends DomainEvent<string, unknown>> implements EventBus<Evt> {
|
|
1186
|
+
private readonly handlers;
|
|
1187
|
+
subscribe<T extends Evt>(eventType: string, handler: EventHandler<T>): () => void;
|
|
1188
|
+
publish(events: ReadonlyArray<Evt>): Promise<void>;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* A Specification is a named, standalone object that represents a business rule for a query.
|
|
1193
|
+
* It is "translatable" into a concrete database query.
|
|
1194
|
+
*/
|
|
1195
|
+
interface ISpecification<T> {
|
|
1196
|
+
readonly _type: T;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* Repository interface for Aggregate Roots (Entities).
|
|
1201
|
+
*
|
|
1202
|
+
* Repositories work exclusively with Aggregate Root Entities. The Aggregate Root
|
|
1203
|
+
* is the Entity that represents the aggregate externally and is the only object
|
|
1204
|
+
* that can be loaded/saved through repositories.
|
|
1205
|
+
*
|
|
1206
|
+
* When loading an Aggregate Root, all child entities and value objects within
|
|
1207
|
+
* the aggregate state are loaded as well. When saving, the entire aggregate
|
|
1208
|
+
* (including all child entities) is persisted as a unit.
|
|
1209
|
+
*
|
|
1210
|
+
* Child entities cannot be loaded or saved independently - they exist only
|
|
1211
|
+
* within the aggregate boundary and are managed through the Aggregate Root.
|
|
1212
|
+
*
|
|
1213
|
+
* @template TState - The type of the aggregate state (contains child entities and value objects)
|
|
1214
|
+
* @template TEvent - The union type of all domain events
|
|
1215
|
+
* @template TAgg - The aggregate root type (must be an Aggregate Root Entity)
|
|
1216
|
+
* @template TId - The type of the aggregate root identifier
|
|
1217
|
+
*/
|
|
1218
|
+
interface IRepository<TState, TEvent extends DomainEvent<string, unknown>, TAgg extends AggregateRoot<TId> & Aggregate<TState, TEvent>, TId extends Id<string>> {
|
|
1219
|
+
getById(id: TId): Promise<TAgg | null>;
|
|
1220
|
+
findOne(spec: ISpecification<TAgg>): Promise<TAgg | null>;
|
|
1221
|
+
find(spec: ISpecification<TAgg>): Promise<TAgg[]>;
|
|
1222
|
+
save(aggregate: TAgg): Promise<void>;
|
|
1223
|
+
delete(id: TId): Promise<void>;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
type VO<T> = Readonly<T>;
|
|
1227
|
+
/**
|
|
1228
|
+
* Deep freezes an object and all its nested properties recursively.
|
|
1229
|
+
* This ensures true immutability for value objects with nested structures.
|
|
1230
|
+
* Handles circular references by tracking visited objects.
|
|
1231
|
+
*/
|
|
1232
|
+
declare function deepFreeze<T>(obj: T, visited?: WeakSet<object>): Readonly<T>;
|
|
1233
|
+
/**
|
|
1234
|
+
* Creates a deeply immutable value object from the given data.
|
|
1235
|
+
* All nested objects and arrays are frozen recursively.
|
|
1236
|
+
*
|
|
1237
|
+
* @param t - The data to convert into a value object
|
|
1238
|
+
* @returns A deeply frozen, immutable value object
|
|
1239
|
+
*
|
|
1240
|
+
* @example
|
|
1241
|
+
* ```typescript
|
|
1242
|
+
* const address = vo({
|
|
1243
|
+
* street: "Main St",
|
|
1244
|
+
* city: "Berlin",
|
|
1245
|
+
* coordinates: { lat: 52.5, lng: 13.4 }
|
|
1246
|
+
* });
|
|
1247
|
+
* // address.coordinates.lat = 99; // ❌ Error: Cannot assign to read-only property
|
|
1248
|
+
* ```
|
|
1249
|
+
*/
|
|
1250
|
+
declare function vo<T>(t: T): VO<T>;
|
|
1251
|
+
/**
|
|
1252
|
+
* Compares two value objects for equality based on their values.
|
|
1253
|
+
* Uses deep equality comparison that handles:
|
|
1254
|
+
* - Nested objects and arrays
|
|
1255
|
+
* - Primitives (including NaN)
|
|
1256
|
+
* - Dates, Maps, Sets, RegExp
|
|
1257
|
+
* - TypedArrays and DataView
|
|
1258
|
+
* - Symbol keys
|
|
1259
|
+
* - Circular references
|
|
1260
|
+
*
|
|
1261
|
+
* @param a - First value object
|
|
1262
|
+
* @param b - Second value object
|
|
1263
|
+
* @returns true if both objects have the same values, false otherwise
|
|
1264
|
+
*
|
|
1265
|
+
* @example
|
|
1266
|
+
* ```typescript
|
|
1267
|
+
* const money1 = vo({ amount: 100, currency: "USD" });
|
|
1268
|
+
* const money2 = vo({ amount: 100, currency: "USD" });
|
|
1269
|
+
* voEquals(money1, money2); // true
|
|
1270
|
+
*
|
|
1271
|
+
* const address1 = vo({
|
|
1272
|
+
* street: "Main St",
|
|
1273
|
+
* coordinates: { lat: 52.5, lng: 13.4 }
|
|
1274
|
+
* });
|
|
1275
|
+
* const address2 = vo({
|
|
1276
|
+
* street: "Main St",
|
|
1277
|
+
* coordinates: { lat: 52.5, lng: 13.4 }
|
|
1278
|
+
* });
|
|
1279
|
+
* voEquals(address1, address2); // true
|
|
1280
|
+
* ```
|
|
1281
|
+
*/
|
|
1282
|
+
declare function voEquals<T>(a: VO<T>, b: VO<T>): boolean;
|
|
1283
|
+
/**
|
|
1284
|
+
* Compares two value objects for equality while ignoring specified keys.
|
|
1285
|
+
* Useful for comparing value objects that contain metadata or optional fields
|
|
1286
|
+
* that should not affect equality comparison.
|
|
1287
|
+
*
|
|
1288
|
+
* @param a - First value object
|
|
1289
|
+
* @param b - Second value object
|
|
1290
|
+
* @param options - Options specifying which keys to ignore during comparison
|
|
1291
|
+
* @returns true if both objects have the same values (after ignoring specified keys), false otherwise
|
|
1292
|
+
*
|
|
1293
|
+
* @example
|
|
1294
|
+
* ```typescript
|
|
1295
|
+
* // Value object with metadata
|
|
1296
|
+
* const address1 = vo({
|
|
1297
|
+
* street: "Main St",
|
|
1298
|
+
* city: "Berlin",
|
|
1299
|
+
* metadata: { createdAt: "2024-01-01", updatedAt: "2024-01-02" }
|
|
1300
|
+
* });
|
|
1301
|
+
*
|
|
1302
|
+
* const address2 = vo({
|
|
1303
|
+
* street: "Main St",
|
|
1304
|
+
* city: "Berlin",
|
|
1305
|
+
* metadata: { createdAt: "2024-01-01", updatedAt: "2024-01-03" }
|
|
1306
|
+
* });
|
|
1307
|
+
*
|
|
1308
|
+
* // Compare ignoring metadata timestamps
|
|
1309
|
+
* voEqualsExcept(address1, address2, {
|
|
1310
|
+
* ignoreKeys: ["updatedAt"],
|
|
1311
|
+
* ignoreKeyPredicate: (key, path) => path.includes("metadata")
|
|
1312
|
+
* }); // true
|
|
1313
|
+
*
|
|
1314
|
+
* // Compare ignoring all metadata
|
|
1315
|
+
* voEqualsExcept(address1, address2, {
|
|
1316
|
+
* ignoreKeyPredicate: (key, path) => path.includes("metadata")
|
|
1317
|
+
* }); // true
|
|
1318
|
+
* ```
|
|
1319
|
+
*/
|
|
1320
|
+
declare function voEqualsExcept<T>(a: VO<T>, b: VO<T>, options: DeepEqualExceptOptions): boolean;
|
|
1321
|
+
/**
|
|
1322
|
+
* Creates a value object with optional validation.
|
|
1323
|
+
* Returns a Result type instead of throwing an error.
|
|
1324
|
+
*
|
|
1325
|
+
* @param t - The data to convert into a value object
|
|
1326
|
+
* @param validate - Validation function that returns true if valid
|
|
1327
|
+
* @param errorMessage - Optional custom error message if validation fails
|
|
1328
|
+
* @returns Result containing the value object if valid, or an error message if validation fails
|
|
1329
|
+
*
|
|
1330
|
+
* @example
|
|
1331
|
+
* ```typescript
|
|
1332
|
+
* const result = voWithValidation(
|
|
1333
|
+
* { amount: 100, currency: "USD" },
|
|
1334
|
+
* (m) => m.amount >= 0 && m.currency.length === 3,
|
|
1335
|
+
* "Invalid money: amount must be non-negative and currency must be 3 characters"
|
|
1336
|
+
* );
|
|
1337
|
+
*
|
|
1338
|
+
* if (result.ok) {
|
|
1339
|
+
* console.log(result.value); // Use the value object
|
|
1340
|
+
* } else {
|
|
1341
|
+
* console.error(result.error); // Handle validation error
|
|
1342
|
+
* }
|
|
1343
|
+
* ```
|
|
1344
|
+
*/
|
|
1345
|
+
declare function voWithValidation<T>(t: T, validate: (value: T) => boolean, errorMessage?: string): Result<VO<T>, string>;
|
|
1346
|
+
/**
|
|
1347
|
+
* Creates a value object with optional validation.
|
|
1348
|
+
* Throws an error if validation fails.
|
|
1349
|
+
*
|
|
1350
|
+
* @param t - The data to convert into a value object
|
|
1351
|
+
* @param validate - Validation function that returns true if valid
|
|
1352
|
+
* @param errorMessage - Optional custom error message if validation fails
|
|
1353
|
+
* @returns A deeply frozen, immutable value object
|
|
1354
|
+
* @throws Error if validation fails
|
|
1355
|
+
*
|
|
1356
|
+
* @example
|
|
1357
|
+
* ```typescript
|
|
1358
|
+
* const money = voWithValidationUnsafe(
|
|
1359
|
+
* { amount: 100, currency: "USD" },
|
|
1360
|
+
* (m) => m.amount >= 0 && m.currency.length === 3,
|
|
1361
|
+
* "Invalid money: amount must be non-negative and currency must be 3 characters"
|
|
1362
|
+
* );
|
|
1363
|
+
* ```
|
|
1364
|
+
*/
|
|
1365
|
+
declare function voWithValidationUnsafe<T>(t: T, validate: (value: T) => boolean, errorMessage?: string): VO<T>;
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Abstract base class for creating Value Objects.
|
|
1369
|
+
* Value Objects are immutable and defined by their properties.
|
|
1370
|
+
*
|
|
1371
|
+
* @template T - The shape of the value object's properties
|
|
1372
|
+
*/
|
|
1373
|
+
declare abstract class ValueObject<T extends object> {
|
|
1374
|
+
readonly props: Readonly<T>;
|
|
1375
|
+
/**
|
|
1376
|
+
* Creates a new ValueObject.
|
|
1377
|
+
* The properties are deeply frozen to ensure immutability.
|
|
1378
|
+
*
|
|
1379
|
+
* @param props - The properties of the value object
|
|
1380
|
+
* @example
|
|
1381
|
+
* ```ts
|
|
1382
|
+
* class Money extends ValueObject<{ amount: number; currency: string }> {
|
|
1383
|
+
* constructor(props: { amount: number; currency: string }) {
|
|
1384
|
+
* super(props);
|
|
1385
|
+
* }
|
|
1386
|
+
* }
|
|
1387
|
+
* ```
|
|
1388
|
+
*/
|
|
1389
|
+
constructor(props: T);
|
|
1390
|
+
/**
|
|
1391
|
+
* Checks if this value object is equal to another.
|
|
1392
|
+
* Uses deep equality comparison on the properties.
|
|
1393
|
+
*
|
|
1394
|
+
* @param other - The other value object to compare
|
|
1395
|
+
* @returns true if the properties are deeply equal
|
|
1396
|
+
*/
|
|
1397
|
+
equals(other: ValueObject<T>): boolean;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
export { type Aggregate, AggregateBase, type AggregateConfig, AggregateEventSourced, type AggregateEventSourcedConfig, type AggregateRoot, type AggregateSnapshot, type Clock, type Command, CommandBus, type CommandHandler, type DomainEvent, type Entity, type EventBus, EventBusImpl, type EventHandler, type EventMetadata, type ICommandBus, type IQueryBus, type IRepository, type ISpecification, type Id, type IdGenerator, type Outbox, type Query, QueryBus, type QueryHandler, type RepoProvider, type UnitOfWork, type VO, ValueObject, type Version, aggregate, bump, copyMetadata, createDomainEvent, createDomainEventWithMetadata, deepFreeze, entityIds, findEntityById, guard, hasEntityId, mergeMetadata, removeEntityById, replaceEntityById, sameAggregate, sameEntity, updateEntityById, vo, voEquals, voEqualsExcept, voWithValidation, voWithValidationUnsafe, withCommit, withEvent };
|