@shirudo/ddd-kit 0.15.0 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -8,6 +8,138 @@ interface IdGenerator {
8
8
  next: <T extends string>() => Id<T>;
9
9
  }
10
10
 
11
+ /**
12
+ * Metadata associated with a domain event for traceability and correlation.
13
+ * Used in event-driven architectures to track event flow across services.
14
+ */
15
+ interface EventMetadata {
16
+ /**
17
+ * Correlation ID for tracing events across multiple services/components.
18
+ * Typically used to group related events in a distributed system.
19
+ */
20
+ correlationId?: string;
21
+ /**
22
+ * Causation ID referencing the event or command that caused this event.
23
+ * Used to build event chains and understand causality.
24
+ */
25
+ causationId?: string;
26
+ /**
27
+ * User ID of the person or system that triggered the event.
28
+ */
29
+ userId?: string;
30
+ /**
31
+ * Source service or component that produced the event.
32
+ */
33
+ source?: string;
34
+ /**
35
+ * Additional custom metadata fields.
36
+ * Allows extensibility for domain-specific metadata.
37
+ */
38
+ [key: string]: unknown;
39
+ }
40
+ /**
41
+ * Domain Event represents something meaningful that happened in the domain.
42
+ * Events are immutable and carry information about what occurred.
43
+ *
44
+ * @template T - The event type name (e.g., "OrderCreated")
45
+ * @template P - The event payload type
46
+ */
47
+ interface DomainEvent<T extends string, P = void> {
48
+ /**
49
+ * The type of the event, used for routing and handling.
50
+ */
51
+ type: T;
52
+ /**
53
+ * The event payload containing the domain data.
54
+ * Omitted when P is void (events without payload).
55
+ */
56
+ payload: P;
57
+ /**
58
+ * Timestamp when the event occurred.
59
+ */
60
+ occurredAt: Date;
61
+ /**
62
+ * Event schema version for handling schema evolution.
63
+ * Required for safe schema migration in event-sourced systems.
64
+ * Use 1 for the initial schema version.
65
+ */
66
+ version: number;
67
+ /**
68
+ * Optional metadata for traceability, correlation, and auditing.
69
+ * Includes correlationId, causationId, userId, source, and custom fields.
70
+ */
71
+ metadata?: EventMetadata;
72
+ }
73
+ /**
74
+ * Creates a domain event with default values.
75
+ * Sets occurredAt to current date and version to 1 if not provided.
76
+ *
77
+ * @param type - The event type
78
+ * @param payload - The event payload
79
+ * @param options - Optional event configuration
80
+ * @returns A domain event
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const event = createDomainEvent("OrderCreated", { orderId: "123" });
85
+ * ```
86
+ */
87
+ declare function createDomainEvent<T extends string>(type: T, payload?: undefined, options?: {
88
+ occurredAt?: Date;
89
+ version?: number;
90
+ metadata?: EventMetadata;
91
+ }): DomainEvent<T, void>;
92
+ declare function createDomainEvent<T extends string, P>(type: T, payload: P, options?: {
93
+ occurredAt?: Date;
94
+ version?: number;
95
+ metadata?: EventMetadata;
96
+ }): DomainEvent<T, P>;
97
+ /**
98
+ * Creates a domain event with metadata for traceability.
99
+ * Convenience function for creating events with correlation and causation IDs.
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const event = createDomainEventWithMetadata(
104
+ * "OrderCreated",
105
+ * { orderId: "123" },
106
+ * { correlationId: "corr-123", causationId: "cmd-456", userId: "user-789" }
107
+ * );
108
+ * ```
109
+ */
110
+ declare function createDomainEventWithMetadata<T extends string, P>(type: T, payload: P, metadata: EventMetadata, options?: {
111
+ occurredAt?: Date;
112
+ version?: number;
113
+ }): DomainEvent<T, P>;
114
+ /**
115
+ * Copies metadata from a source event to a new event.
116
+ * Useful for maintaining correlation chains in event-driven architectures.
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * const newEvent = createDomainEvent(
121
+ * "OrderShipped",
122
+ * { orderId: "123" },
123
+ * { metadata: copyMetadata(previousEvent, { causationId: previousEvent.type }) }
124
+ * );
125
+ * ```
126
+ */
127
+ declare function copyMetadata(sourceEvent: DomainEvent<string, unknown>, additionalMetadata?: Partial<EventMetadata>): EventMetadata;
128
+ /**
129
+ * Merges multiple metadata objects into one.
130
+ * Later metadata objects override earlier ones for the same keys.
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * const metadata = mergeMetadata(
135
+ * { correlationId: "corr-123" },
136
+ * { userId: "user-456" },
137
+ * { source: "order-service" }
138
+ * );
139
+ * ```
140
+ */
141
+ declare function mergeMetadata(...metadataObjects: Array<EventMetadata | undefined>): EventMetadata;
142
+
11
143
  /**
12
144
  * Functional definition of an Entity via its capability.
13
145
  * An object is identifiable if it has an id.
@@ -292,36 +424,28 @@ interface AggregateConfig {
292
424
  autoVersionBump?: boolean;
293
425
  }
294
426
  /**
295
- * Base class for creating Aggregate Roots (Entities) without Event Sourcing.
427
+ * Base class for Aggregate Roots without Event Sourcing.
296
428
  *
297
- * This class creates an Entity that serves as the Aggregate Root. The Aggregate Root
298
- * is the parent Entity of the aggregate and represents it externally. It has identity
299
- * (id), state, and version for optimistic concurrency control.
429
+ * In DDD (Evans), an Aggregate is a cluster of objects root entity, child entities,
430
+ * and value objects treated as a unit for consistency. The **Aggregate Root** is the
431
+ * root entity that represents the aggregate externally and is the only entry point
432
+ * for external code. This class serves as both: it IS the root entity and it contains
433
+ * the aggregate state (`TState`) which holds child entities and value objects.
300
434
  *
301
- * Extends `Entity<TState, TId>` to inherit:
302
- * - Identity (id)
303
- * - State management
304
- * - State validation
305
- *
306
- * Adds Aggregate Root specific functionality:
307
- * - Version management (for Optimistic Concurrency Control)
308
- * - Domain events tracking
435
+ * Provides:
436
+ * - Identity (id) and state management (via `Entity`)
437
+ * - Version management for optimistic concurrency control
438
+ * - Domain event tracking for side-effects
309
439
  * - Snapshot support for performance optimization
310
440
  *
311
- * The aggregate state (`TState`) contains:
312
- * - Child entities (Entities with id + state, but no own version)
313
- * - Value objects (immutable objects)
441
+ * All changes to child entities within `TState` are versioned through this root.
442
+ * Use `setState()` for state mutations to ensure invariant validation.
314
443
  *
315
- * All changes to child entities are versioned through the Aggregate Root. The version
316
- * applies to the entire aggregate, including all child entities.
317
- *
318
- * Implements `IAggregateRoot<TId>` to mark this as an Aggregate Root Entity.
319
- *
320
- * Use this class when you don't need Event Sourcing but still want
321
- * aggregate patterns with versioning and state management.
444
+ * For event sourcing, use `EventSourcedAggregate` instead.
322
445
  *
323
446
  * @template TState - The type of the aggregate state (contains child entities and value objects)
324
447
  * @template TId - The type of the aggregate root identifier
448
+ * @template TEvent - The type of domain events recorded by this aggregate (defaults to unknown)
325
449
  *
326
450
  * @example
327
451
  * ```typescript
@@ -332,14 +456,15 @@ interface AggregateConfig {
332
456
  * }
333
457
  *
334
458
  * confirm(): void {
335
- * this._state = { ...this._state, status: "confirmed" };
336
- * this.bumpVersion(); // Versions the entire aggregate
459
+ * this.setState({ ...this.state, status: "confirmed" }, true);
337
460
  * }
338
461
  * }
339
462
  * ```
340
463
  */
341
- declare abstract class AggregateRoot<TState, TId extends Id<string>> extends Entity<TState, TId> implements IAggregateRoot<TId> {
342
- version: Version;
464
+ declare abstract class AggregateRoot<TState, TId extends Id<string>, TEvent = unknown> extends Entity<TState, TId> implements IAggregateRoot<TId> {
465
+ private _version;
466
+ get version(): Version;
467
+ protected setVersion(version: Version): void;
343
468
  private readonly _config;
344
469
  private readonly _autoVersionBump;
345
470
  private _domainEvents;
@@ -347,7 +472,7 @@ declare abstract class AggregateRoot<TState, TId extends Id<string>> extends Ent
347
472
  * Returns a read-only list of domain events recorded by this aggregate.
348
473
  * These events are side-effects of state changes.
349
474
  */
350
- get domainEvents(): ReadonlyArray<unknown>;
475
+ get domainEvents(): ReadonlyArray<TEvent>;
351
476
  /**
352
477
  * Clears the list of recorded domain events.
353
478
  * Call this after dispatching the events.
@@ -360,7 +485,7 @@ declare abstract class AggregateRoot<TState, TId extends Id<string>> extends Ent
360
485
  *
361
486
  * @param event - The domain event to add
362
487
  */
363
- protected addDomainEvent(event: unknown): void;
488
+ protected addDomainEvent(event: TEvent): void;
364
489
  /**
365
490
  * Manually bumps the aggregate version.
366
491
  * Call this after state changes for Optimistic Concurrency Control.
@@ -416,7 +541,7 @@ declare abstract class AggregateRoot<TState, TId extends Id<string>> extends Ent
416
541
  * @template TId - The type of the aggregate root identifier
417
542
  * @template TEvent - The union type of all domain events
418
543
  */
419
- interface IAggregateEventSourced<TId extends Id<string>, TEvent extends DomainEvent<string, unknown>> extends IAggregateRoot<TId> {
544
+ interface IEventSourcedAggregate<TId extends Id<string>, TEvent extends DomainEvent<string, unknown>> extends IAggregateRoot<TId> {
420
545
  /**
421
546
  * Returns a read-only list of new, not-yet-persisted events.
422
547
  */
@@ -446,9 +571,9 @@ interface IAggregateEventSourced<TId extends Id<string>, TEvent extends DomainEv
446
571
  }
447
572
  type Handler<TState, TEvent> = (state: TState, event: TEvent) => TState;
448
573
  /**
449
- * Extended configuration options for AggregateEventSourced behavior.
574
+ * Configuration options for EventSourcedAggregate behavior.
450
575
  */
451
- interface AggregateEventSourcedConfig extends AggregateConfig {
576
+ interface EventSourcedAggregateConfig {
452
577
  /**
453
578
  * Whether to automatically bump the version when applying new events.
454
579
  * Defaults to true. Set to false to manually control versioning.
@@ -456,26 +581,15 @@ interface AggregateEventSourcedConfig extends AggregateConfig {
456
581
  autoVersionBump?: boolean;
457
582
  }
458
583
  /**
459
- * Base class for Event-Sourced Aggregate Roots (Entities).
584
+ * Base class for Event-Sourced Aggregate Roots (Vernon, IDDD Chapter 8).
460
585
  *
461
- * Extends `AggregateRoot` to create an Aggregate Root Entity with Event Sourcing capabilities.
462
- * The Aggregate Root is the parent Entity of the aggregate and represents it externally.
586
+ * Like `AggregateRoot`, this is both the root entity and the aggregate boundary.
587
+ * The difference is persistence: state is derived from events, not stored directly.
588
+ * Events are the single source of truth — all state changes go through `apply()` → handler.
463
589
  *
464
- * The aggregate state (`TState`) contains:
465
- * - Child entities (Entities with id, but no own version)
466
- * - Value objects (immutable objects)
467
- *
468
- * All changes to child entities are versioned through the Aggregate Root. The version
469
- * applies to the entire aggregate, including all child entities.
470
- *
471
- * Extends `AggregateRoot` with Event Sourcing capabilities:
472
- * - Event tracking (pendingEvents)
473
- * - Event handlers for state transitions
474
- * - Event validation
475
- * - History replay
476
- *
477
- * Use this class when you want Event Sourcing with full event tracking
478
- * and replay capabilities.
590
+ * Extends `Entity` directly (not `AggregateRoot`) so that `setState()` and
591
+ * `addDomainEvent()` are not available. This enforces the event sourcing pattern
592
+ * at the type level — there is no way to mutate state without going through an event handler.
479
593
  *
480
594
  * @template TState - The type of the aggregate state (contains child entities and value objects)
481
595
  * @template TEvent - The union type of all domain events
@@ -483,8 +597,7 @@ interface AggregateEventSourcedConfig extends AggregateConfig {
483
597
  *
484
598
  * @example
485
599
  * ```typescript
486
- * // Order is an Aggregate Root (an Entity) with Event Sourcing
487
- * class Order extends AggregateEventSourced<OrderState, OrderEvent, OrderId> {
600
+ * class Order extends EventSourcedAggregate<OrderState, OrderEvent, OrderId> {
488
601
  * confirm(): void {
489
602
  * this.apply(createDomainEvent("OrderConfirmed", {}));
490
603
  * }
@@ -498,58 +611,32 @@ interface AggregateEventSourcedConfig extends AggregateConfig {
498
611
  * }
499
612
  * ```
500
613
  */
501
- declare abstract class AggregateEventSourced<TState, TEvent extends DomainEvent<string, unknown>, TId extends Id<string>> extends AggregateRoot<TState, TId> implements IAggregateEventSourced<TId, TEvent> {
502
- private readonly _eventConfig;
503
- private readonly _eventAutoVersionBump;
504
- protected constructor(id: TId, initialState: TState, config?: AggregateEventSourcedConfig);
505
- /**
506
- * Returns a read-only list of new, not-yet-persisted events.
507
- */
614
+ declare abstract class EventSourcedAggregate<TState, TEvent extends DomainEvent<string, unknown>, TId extends Id<string>> extends Entity<TState, TId> implements IEventSourcedAggregate<TId, TEvent> {
615
+ private _version;
616
+ get version(): Version;
617
+ private setVersion;
618
+ private _pendingEvents;
619
+ private readonly _autoVersionBump;
508
620
  get pendingEvents(): ReadonlyArray<TEvent>;
509
- /**
510
- * Clears the list of pending events.
511
- * Typically called after the events have been persisted.
512
- */
513
621
  clearPendingEvents(): void;
622
+ protected constructor(id: TId, initialState: TState, config?: EventSourcedAggregateConfig);
514
623
  /**
515
624
  * Validates an event before it is applied.
516
625
  * Override this method to add custom validation logic.
517
626
  * Return `ok(true)` if the event is valid, `err(message)` otherwise.
518
- *
519
- * @param event - The event to validate
520
- * @returns Result indicating if the event is valid
521
- *
522
- * @example
523
- * ```typescript
524
- * protected validateEvent(event: OrderEvent): Result<true, string> {
525
- * if (event.type === "OrderShipped" && this.state.status !== "confirmed") {
526
- * return err("Order must be confirmed before shipping");
527
- * }
528
- * return ok(true);
529
- * }
530
- * ```
531
627
  */
532
628
  protected validateEvent(_event: TEvent): Result<true, string>;
533
629
  /**
534
- * Applies an event to change the state and adds it
535
- * to the list of pending events.
630
+ * Applies an event to change the state and adds it to pending events.
536
631
  * Returns a Result type instead of throwing an error.
537
632
  *
538
633
  * @param event - The domain event to apply
539
- * @param isNew - Indicates whether the event is new (and needs to be persisted)
540
- * or if it is being loaded from history
541
- * @returns Result<void, string> - ok if successful, err with error message if validation fails or handler is missing
634
+ * @param isNew - Whether the event is new (needs persisting) or from history replay
542
635
  */
543
636
  protected apply(event: TEvent, isNew?: boolean): Result<void, string>;
544
637
  /**
545
- * Applies an event to change the state and adds it
546
- * to the list of pending events.
638
+ * Applies an event to change the state and adds it to pending events.
547
639
  * Throws an error if validation fails or handler is missing.
548
- *
549
- * @param event - The domain event to apply
550
- * @param isNew - Indicates whether the event is new (and needs to be persisted)
551
- * or if it is being loaded from history
552
- * @throws Error if event validation fails or handler is missing
553
640
  */
554
641
  protected applyUnsafe(event: TEvent, isNew?: boolean): void;
555
642
  /**
@@ -560,41 +647,17 @@ declare abstract class AggregateEventSourced<TState, TEvent extends DomainEvent<
560
647
  /**
561
648
  * Reconstitutes the aggregate from an event history.
562
649
  * Sets the version to the number of events in the history.
563
- *
564
- * @param history - An ordered list of past events
565
650
  */
566
651
  loadFromHistory(history: TEvent[]): Result<void, string>;
567
- /**
568
- * Checks if the aggregate has any pending events.
569
- *
570
- * @returns true if there are pending events, false otherwise
571
- */
572
652
  hasPendingEvents(): boolean;
573
- /**
574
- * Returns the number of pending events.
575
- *
576
- * @returns The count of pending events
577
- */
578
653
  getEventCount(): number;
654
+ getLatestEvent(): TEvent | undefined;
579
655
  /**
580
- * Returns the latest pending event, if any.
581
- *
582
- * @returns The most recent event or undefined if no events exist
656
+ * Creates a snapshot of the current aggregate state.
583
657
  */
584
- getLatestEvent(): TEvent | undefined;
658
+ createSnapshot(): AggregateSnapshot<TState>;
585
659
  /**
586
- * Restores the aggregate from a snapshot and applies events that occurred after the snapshot.
587
- * This is more efficient than replaying all events from the beginning.
588
- *
589
- * @param snapshot - The snapshot to restore from
590
- * @param eventsAfterSnapshot - Events that occurred after the snapshot was taken
591
- *
592
- * @example
593
- * ```typescript
594
- * const snapshot = await snapshotRepository.getLatest(aggregateId);
595
- * const eventsAfter = await eventStore.getEventsAfter(aggregateId, snapshot.version);
596
- * aggregate.restoreFromSnapshotWithEvents(snapshot, eventsAfter);
597
- * ```
660
+ * Restores the aggregate from a snapshot and applies events that occurred after.
598
661
  */
599
662
  restoreFromSnapshotWithEvents(snapshot: AggregateSnapshot<TState>, eventsAfterSnapshot: TEvent[]): Result<void, string>;
600
663
  /**
@@ -612,164 +675,32 @@ type Version = number & {
612
675
  readonly __v: true;
613
676
  };
614
677
  /**
615
- * Metadata associated with a domain event for traceability and correlation.
616
- * Used in event-driven architectures to track event flow across services.
617
- */
618
- interface EventMetadata {
619
- /**
620
- * Correlation ID for tracing events across multiple services/components.
621
- * Typically used to group related events in a distributed system.
622
- */
623
- correlationId?: string;
624
- /**
625
- * Causation ID referencing the event or command that caused this event.
626
- * Used to build event chains and understand causality.
627
- */
628
- causationId?: string;
629
- /**
630
- * User ID of the person or system that triggered the event.
631
- */
632
- userId?: string;
633
- /**
634
- * Source service or component that produced the event.
635
- */
636
- source?: string;
637
- /**
638
- * Additional custom metadata fields.
639
- * Allows extensibility for domain-specific metadata.
640
- */
641
- [key: string]: unknown;
642
- }
643
- /**
644
- * Domain Event represents something meaningful that happened in the domain.
645
- * Events are immutable and carry information about what occurred.
678
+ * Lightweight functional aggregate state state + version, no event sourcing.
679
+ * This is a data projection, not a full Aggregate (which requires identity via IAggregateRoot).
646
680
  *
647
- * @template T - The event type name (e.g., "OrderCreated")
648
- * @template P - The event payload type
649
- */
650
- interface DomainEvent<T extends string, P> {
651
- /**
652
- * The type of the event, used for routing and handling.
653
- */
654
- type: T;
655
- /**
656
- * The event payload containing the domain data.
657
- */
658
- payload: P;
659
- /**
660
- * Timestamp when the event occurred.
661
- */
662
- occurredAt: Date;
663
- /**
664
- * Event schema version for handling schema evolution.
665
- * Defaults to 1 if not specified. Higher versions indicate schema changes.
666
- */
667
- version?: number;
668
- /**
669
- * Optional metadata for traceability, correlation, and auditing.
670
- * Includes correlationId, causationId, userId, source, and custom fields.
671
- */
672
- metadata?: EventMetadata;
673
- }
674
-
675
- /**
676
- * Structural interface representing an aggregate with state and events.
677
- * Used for type constraints in repositories and other infrastructure code.
681
+ * For event sourcing, use the class-based `EventSourcedAggregate` which enforces
682
+ * that state changes happen through event handlers.
678
683
  *
679
684
  * @template State - The type of the aggregate state
680
- * @template Evt - The union type of all domain events
681
685
  */
682
- interface Aggregate<State, Evt extends DomainEvent<string, unknown>> {
686
+ interface AggregateState<State> {
683
687
  state: Readonly<State>;
684
688
  version: Version;
685
- pendingEvents: ReadonlyArray<Evt>;
686
689
  }
687
- declare function aggregate<State, Evt extends DomainEvent<string, unknown>>(state: State, version?: Version): Aggregate<State, Evt>;
688
- declare function withEvent<S, E extends DomainEvent<string, unknown>>(agg: Aggregate<S, E>, evt: E): Aggregate<S, E>;
689
- declare function bump<S, E extends DomainEvent<string, unknown>>(agg: Aggregate<S, E>): Aggregate<S, E>;
690
- /**
691
- * Creates a domain event with default values.
692
- * Sets occurredAt to current date and version to 1 if not provided.
693
- *
694
- * @param type - The event type
695
- * @param payload - The event payload
696
- * @param options - Optional event configuration
697
- * @returns A domain event
698
- *
699
- * @example
700
- * ```typescript
701
- * const event = createDomainEvent("OrderCreated", { orderId: "123" });
702
- * ```
703
- */
704
- declare function createDomainEvent<T extends string, P>(type: T, payload: P, options?: {
705
- occurredAt?: Date;
706
- version?: number;
707
- metadata?: EventMetadata;
708
- }): DomainEvent<T, P>;
709
- /**
710
- * Creates a domain event with metadata for traceability.
711
- * Convenience function for creating events with correlation and causation IDs.
712
- *
713
- * @param type - The event type
714
- * @param payload - The event payload
715
- * @param metadata - Event metadata for traceability
716
- * @param options - Optional event configuration
717
- * @returns A domain event with metadata
718
- *
719
- * @example
720
- * ```typescript
721
- * const event = createDomainEventWithMetadata(
722
- * "OrderCreated",
723
- * { orderId: "123" },
724
- * {
725
- * correlationId: "corr-123",
726
- * causationId: "cmd-456",
727
- * userId: "user-789"
728
- * }
729
- * );
730
- * ```
731
- */
732
- declare function createDomainEventWithMetadata<T extends string, P>(type: T, payload: P, metadata: EventMetadata, options?: {
733
- occurredAt?: Date;
734
- version?: number;
735
- }): DomainEvent<T, P>;
736
690
  /**
737
- * Copies metadata from a source event to a new event.
738
- * Useful for maintaining correlation chains in event-driven architectures.
739
- *
740
- * @param sourceEvent - The source event to copy metadata from
741
- * @param additionalMetadata - Additional metadata to merge in
742
- * @returns Event metadata with copied and merged values
691
+ * Creates a lightweight functional aggregate state.
743
692
  *
744
693
  * @example
745
694
  * ```typescript
746
- * const newEvent = createDomainEvent(
747
- * "OrderShipped",
748
- * { orderId: "123" },
749
- * {
750
- * metadata: copyMetadata(previousEvent, { causationId: previousEvent.type })
751
- * }
752
- * );
695
+ * const order = aggregate<OrderState>({ status: "draft", items: [] });
753
696
  * ```
754
697
  */
755
- declare function copyMetadata(sourceEvent: DomainEvent<string, unknown>, additionalMetadata?: Partial<EventMetadata>): EventMetadata;
698
+ declare function aggregate<State>(state: State, version?: Version): AggregateState<State>;
756
699
  /**
757
- * Merges multiple metadata objects into one.
758
- * Later metadata objects override earlier ones for the same keys.
759
- *
760
- * @param metadataObjects - Array of metadata objects to merge
761
- * @returns Merged event metadata
762
- *
763
- * @example
764
- * ```typescript
765
- * const metadata = mergeMetadata(
766
- * { correlationId: "corr-123" },
767
- * { userId: "user-456" },
768
- * { source: "order-service" }
769
- * );
770
- * ```
700
+ * Bumps the version of a functional aggregate state.
701
+ * Returns a new aggregate state with incremented version.
771
702
  */
772
- declare function mergeMetadata(...metadataObjects: Array<EventMetadata | undefined>): EventMetadata;
703
+ declare function bump<S>(agg: AggregateState<S>): AggregateState<S>;
773
704
  /**
774
705
  * Snapshot of an aggregate state at a specific point in time.
775
706
  * Used for optimizing event replay by starting from a snapshot
@@ -792,25 +723,24 @@ interface AggregateSnapshot<TState> {
792
723
  snapshotAt: Date;
793
724
  }
794
725
  /**
795
- * Checks if two aggregates are the same (same ID and version).
726
+ * Checks if two aggregates are at the same version (same ID and version).
796
727
  * Useful for optimistic concurrency control checks.
797
728
  *
798
- * @param a - First aggregate
799
- * @param b - Second aggregate
800
- * @returns true if both aggregates have the same ID and version
729
+ * Note: Two aggregates with the same ID ARE the same aggregate (identity).
730
+ * This function checks if they are at the same version — i.e., no concurrent modification.
801
731
  *
802
732
  * @example
803
733
  * ```typescript
804
- * const aggregate1 = await repository.getById(id);
734
+ * const before = await repository.getById(id);
805
735
  * // ... some operations ...
806
- * const aggregate2 = await repository.getById(id);
736
+ * const after = await repository.getById(id);
807
737
  *
808
- * if (!sameAggregate(aggregate1, aggregate2)) {
738
+ * if (!sameVersion(before, after)) {
809
739
  * throw new Error("Aggregate was modified by another process");
810
740
  * }
811
741
  * ```
812
742
  */
813
- declare function sameAggregate<TId extends Id<string>>(a: {
743
+ declare function sameVersion<TId extends Id<string>>(a: {
814
744
  id: TId;
815
745
  version: Version;
816
746
  }, b: {
@@ -906,29 +836,58 @@ interface Command {
906
836
  */
907
837
  type CommandHandler<C extends Command, R> = (cmd: C) => Promise<Result<R, string>>;
908
838
 
839
+ /**
840
+ * Type map for command types to their return types.
841
+ * Used to improve type inference in CommandBus.
842
+ *
843
+ * @example
844
+ * ```typescript
845
+ * type MyCommandMap = {
846
+ * CreateOrder: OrderId;
847
+ * CancelOrder: void;
848
+ * };
849
+ *
850
+ * const bus = new CommandBus<MyCommandMap>();
851
+ * const result = await bus.execute({ type: "CreateOrder", ... });
852
+ * // result: Result<OrderId, string> ← automatically inferred
853
+ * ```
854
+ */
855
+ type CommandTypeMap = Record<string, unknown>;
909
856
  /**
910
857
  * Command Bus interface for dispatching commands to their handlers.
911
858
  * Provides a centralized way to execute commands with handler registration.
912
859
  *
860
+ * Supports an optional type map (`TMap`) for automatic return type inference.
861
+ * Without a type map, the return type must be specified manually or defaults to `unknown`.
862
+ *
863
+ * @template TMap - Optional mapping from command type strings to return types
864
+ *
913
865
  * @example
914
866
  * ```typescript
867
+ * // With type map (recommended) – return type is inferred
868
+ * type MyCommands = { CreateOrder: OrderId; CancelOrder: void };
869
+ * const bus = new CommandBus<MyCommands>();
870
+ * const result = await bus.execute({ type: "CreateOrder", ... });
871
+ * // result: Result<OrderId, string>
872
+ *
873
+ * // Without type map – works like before
915
874
  * const bus = new CommandBus();
916
875
  * bus.register("CreateOrder", createOrderHandler);
917
- *
918
- * const result = await bus.execute({
919
- * type: "CreateOrder",
920
- * customerId: "123",
921
- * items: [...]
922
- * });
876
+ * const result = await bus.execute({ type: "CreateOrder", ... });
877
+ * // result: Result<unknown, string>
923
878
  * ```
924
879
  */
925
- interface ICommandBus {
880
+ interface ICommandBus<TMap extends CommandTypeMap = CommandTypeMap> {
926
881
  /**
927
882
  * Executes a command by dispatching it to the registered handler.
883
+ * When a type map is provided, the return type is inferred from the command type.
928
884
  *
929
885
  * @param command - The command to execute
930
886
  * @returns Result containing the success value or error message
931
887
  */
888
+ execute<C extends Command & {
889
+ type: keyof TMap & string;
890
+ }>(command: C): Promise<Result<TMap[C["type"]], string>>;
932
891
  execute<C extends Command, R>(command: C): Promise<Result<R, string>>;
933
892
  /**
934
893
  * Registers a handler for a specific command type.
@@ -942,6 +901,10 @@ interface ICommandBus {
942
901
  * Simple in-memory command bus implementation.
943
902
  * Handlers are stored in a Map and dispatched based on command type.
944
903
  *
904
+ * Supports an optional type map (`TMap`) for automatic return type inference.
905
+ * When `TMap` is provided, `execute()` infers the result type from the command type.
906
+ * Without `TMap`, it works like before (return type defaults to `unknown` or can be specified manually).
907
+ *
945
908
  * **Note:** This is a basic implementation suitable for development and simple use cases.
946
909
  * For production environments, consider implementing or using a more feature-rich bus that includes:
947
910
  * - Middleware/Pipeline support (logging, validation, authorization)
@@ -954,20 +917,28 @@ interface ICommandBus {
954
917
  * The `CommandHandler` type can still be used with external production-grade buses
955
918
  * (e.g., RabbitMQ, AWS SQS) while maintaining type safety.
956
919
  *
920
+ * @template TMap - Optional mapping from command type strings to return types
921
+ *
957
922
  * @example
958
923
  * ```typescript
959
- * const bus = new CommandBus();
960
- * bus.register("CreateOrder", async (cmd) => {
961
- * // ... handler logic
962
- * return ok(orderId);
963
- * });
924
+ * // With type map – full inference
925
+ * type Commands = { CreateOrder: OrderId; CancelOrder: void };
926
+ * const bus = new CommandBus<Commands>();
927
+ * const result = await bus.execute({ type: "CreateOrder", ... });
928
+ * // result: Result<OrderId, string>
964
929
  *
930
+ * // Without type map – same as before
931
+ * const bus = new CommandBus();
932
+ * bus.register("CreateOrder", async (cmd) => ok(orderId));
965
933
  * const result = await bus.execute({ type: "CreateOrder", ... });
966
934
  * ```
967
935
  */
968
- declare class CommandBus implements ICommandBus {
936
+ declare class CommandBus<TMap extends CommandTypeMap = CommandTypeMap> implements ICommandBus<TMap> {
969
937
  private readonly handlers;
970
938
  register<C extends Command, R>(commandType: C["type"], handler: CommandHandler<C, R>): void;
939
+ execute<C extends Command & {
940
+ type: keyof TMap & string;
941
+ }>(command: C): Promise<Result<TMap[C["type"]], string>>;
971
942
  execute<C extends Command, R>(command: C): Promise<Result<R, string>>;
972
943
  }
973
944
 
@@ -1000,7 +971,9 @@ type EventHandler<Evt> = (event: Evt) => Promise<void> | void;
1000
971
  * await bus.publish([orderCreatedEvent, orderShippedEvent]);
1001
972
  * ```
1002
973
  */
1003
- interface EventBus<Evt> {
974
+ interface EventBus<Evt extends {
975
+ type: string;
976
+ }> {
1004
977
  /**
1005
978
  * Publishes events to all subscribed handlers.
1006
979
  * All handlers for each event type will be called.
@@ -1026,14 +999,26 @@ interface EventBus<Evt> {
1026
999
  * unsubscribe();
1027
1000
  * ```
1028
1001
  */
1029
- subscribe: <T extends Evt>(eventType: string, handler: EventHandler<T>) => () => void;
1002
+ subscribe: <T extends Evt>(eventType: Evt["type"], handler: EventHandler<T>) => () => void;
1003
+ /**
1004
+ * Subscribes to the next occurrence of an event type.
1005
+ * Returns a Promise that resolves with the event data.
1006
+ * Automatically unsubscribes after the first event.
1007
+ *
1008
+ * @param eventType - The event type to wait for
1009
+ * @returns A Promise that resolves with the event
1010
+ *
1011
+ * @example
1012
+ * ```typescript
1013
+ * const event = await bus.once("OrderCreated");
1014
+ * console.log("Order created:", event.payload.orderId);
1015
+ * ```
1016
+ */
1017
+ once: <T extends Evt>(eventType: Evt["type"]) => Promise<T>;
1030
1018
  }
1031
1019
  interface Outbox<Evt> {
1032
1020
  add: (events: ReadonlyArray<Evt>) => Promise<void>;
1033
1021
  }
1034
- interface Clock {
1035
- now: () => Date;
1036
- }
1037
1022
 
1038
1023
  interface UnitOfWork {
1039
1024
  transactional<T>(fn: () => Promise<T>): Promise<T>;
@@ -1063,7 +1048,9 @@ type RepoProvider<R> = (uow: UnitOfWork) => R;
1063
1048
  * );
1064
1049
  * ```
1065
1050
  */
1066
- declare function withCommit<Evt, R>(deps: {
1051
+ declare function withCommit<Evt extends {
1052
+ type: string;
1053
+ }, R>(deps: {
1067
1054
  outbox: Outbox<Evt>;
1068
1055
  bus?: EventBus<Evt>;
1069
1056
  uow: UnitOfWork;
@@ -1147,29 +1134,57 @@ interface Query {
1147
1134
  */
1148
1135
  type QueryHandler<Q extends Query, R> = (query: Q) => Promise<R>;
1149
1136
 
1137
+ /**
1138
+ * Type map for query types to their return types.
1139
+ * Used to improve type inference in QueryBus.
1140
+ *
1141
+ * @example
1142
+ * ```typescript
1143
+ * type MyQueryMap = {
1144
+ * GetOrder: Order | null;
1145
+ * ListOrders: Order[];
1146
+ * };
1147
+ *
1148
+ * const bus = new QueryBus<MyQueryMap>();
1149
+ * const result = await bus.execute({ type: "GetOrder", orderId: "123" });
1150
+ * // result: Result<Order | null, string> ← automatically inferred
1151
+ * ```
1152
+ */
1153
+ type QueryTypeMap = Record<string, unknown>;
1150
1154
  /**
1151
1155
  * Query Bus interface for dispatching queries to their handlers.
1152
1156
  * Provides a centralized way to execute queries with handler registration.
1153
1157
  *
1158
+ * Supports an optional type map (`TMap`) for automatic return type inference.
1159
+ * Without a type map, the return type must be specified manually or defaults to `unknown`.
1160
+ *
1161
+ * @template TMap - Optional mapping from query type strings to return types
1162
+ *
1154
1163
  * @example
1155
1164
  * ```typescript
1156
- * const bus = new QueryBus();
1157
- * bus.register("GetOrder", getOrderHandler);
1165
+ * // With type map (recommended) – return type is inferred
1166
+ * type MyQueries = { GetOrder: Order | null; ListOrders: Order[] };
1167
+ * const bus = new QueryBus<MyQueries>();
1168
+ * const result = await bus.execute({ type: "GetOrder", orderId: "123" });
1169
+ * // result: Result<Order | null, string>
1158
1170
  *
1159
- * const order = await bus.execute({
1160
- * type: "GetOrder",
1161
- * orderId: "123"
1162
- * });
1171
+ * // Without type map – works like before
1172
+ * const bus = new QueryBus();
1173
+ * const result = await bus.execute({ type: "GetOrder", orderId: "123" });
1174
+ * // result: Result<unknown, string>
1163
1175
  * ```
1164
1176
  */
1165
- interface IQueryBus {
1177
+ interface IQueryBus<TMap extends QueryTypeMap = QueryTypeMap> {
1166
1178
  /**
1167
1179
  * Executes a query by dispatching it to the registered handler.
1168
- * Returns a Result type instead of throwing an error.
1180
+ * When a type map is provided, the return type is inferred from the query type.
1169
1181
  *
1170
1182
  * @param query - The query to execute
1171
- * @returns Result containing the query result if successful, or an error message if no handler is registered
1183
+ * @returns Result containing the query result if successful, or an error message
1172
1184
  */
1185
+ execute<Q extends Query & {
1186
+ type: keyof TMap & string;
1187
+ }>(query: Q): Promise<Result<TMap[Q["type"]], string>>;
1173
1188
  execute<Q extends Query, R>(query: Q): Promise<Result<R, string>>;
1174
1189
  /**
1175
1190
  * Executes a query by dispatching it to the registered handler.
@@ -1179,6 +1194,9 @@ interface IQueryBus {
1179
1194
  * @returns The query result
1180
1195
  * @throws Error if no handler is registered for the query type
1181
1196
  */
1197
+ executeUnsafe<Q extends Query & {
1198
+ type: keyof TMap & string;
1199
+ }>(query: Q): Promise<TMap[Q["type"]]>;
1182
1200
  executeUnsafe<Q extends Query, R>(query: Q): Promise<R>;
1183
1201
  /**
1184
1202
  * Registers a handler for a specific query type.
@@ -1188,15 +1206,14 @@ interface IQueryBus {
1188
1206
  */
1189
1207
  register<Q extends Query, R>(queryType: Q["type"], handler: QueryHandler<Q, R>): void;
1190
1208
  }
1191
- /**
1192
- * Type map for query types to their return types.
1193
- * Used to improve type inference in QueryBus.
1194
- */
1195
- type QueryTypeMap = Record<string, unknown>;
1196
1209
  /**
1197
1210
  * Simple in-memory query bus implementation.
1198
1211
  * Handlers are stored in a Map and dispatched based on query type.
1199
1212
  *
1213
+ * Supports an optional type map (`TMap`) for automatic return type inference.
1214
+ * When `TMap` is provided, `execute()` and `executeUnsafe()` infer the result type from the query type.
1215
+ * Without `TMap`, it works like before (return type defaults to `unknown` or can be specified manually).
1216
+ *
1200
1217
  * **Note:** This is a basic implementation suitable for development and simple use cases.
1201
1218
  * For production environments, consider implementing or using a more feature-rich bus that includes:
1202
1219
  * - Middleware/Pipeline support (logging, caching, rate limiting)
@@ -1209,23 +1226,32 @@ type QueryTypeMap = Record<string, unknown>;
1209
1226
  * The `QueryHandler` type can still be used with external production-grade buses
1210
1227
  * (e.g., RabbitMQ, AWS SQS) while maintaining type safety.
1211
1228
  *
1229
+ * @template TMap - Optional mapping from query type strings to return types
1230
+ *
1212
1231
  * @example
1213
1232
  * ```typescript
1214
- * const bus = new QueryBus();
1215
- * bus.register("GetOrder", async (query) => {
1216
- * return await repository.getById(query.orderId);
1217
- * });
1233
+ * // With type map – full inference
1234
+ * type Queries = { GetOrder: Order | null; ListOrders: Order[] };
1235
+ * const bus = new QueryBus<Queries>();
1236
+ * const result = await bus.execute({ type: "GetOrder", orderId: "123" });
1237
+ * // result: Result<Order | null, string>
1218
1238
  *
1219
- * const order = await bus.execute({ type: "GetOrder", orderId: "123" });
1239
+ * // Without type map same as before
1240
+ * const bus = new QueryBus();
1241
+ * bus.register("GetOrder", async (query) => repository.getById(query.orderId));
1242
+ * const result = await bus.execute({ type: "GetOrder", orderId: "123" });
1220
1243
  * ```
1221
1244
  */
1222
- declare class QueryBus<TMap extends QueryTypeMap = QueryTypeMap> implements IQueryBus {
1245
+ declare class QueryBus<TMap extends QueryTypeMap = QueryTypeMap> implements IQueryBus<TMap> {
1223
1246
  private readonly handlers;
1224
1247
  register<Q extends Query, R>(queryType: Q["type"], handler: QueryHandler<Q, R>): void;
1225
1248
  execute<Q extends Query & {
1226
- type: keyof TMap;
1249
+ type: keyof TMap & string;
1227
1250
  }>(query: Q): Promise<Result<TMap[Q["type"]], string>>;
1228
1251
  execute<Q extends Query, R>(query: Q): Promise<Result<R, string>>;
1252
+ executeUnsafe<Q extends Query & {
1253
+ type: keyof TMap & string;
1254
+ }>(query: Q): Promise<TMap[Q["type"]]>;
1229
1255
  executeUnsafe<Q extends Query, R>(query: Q): Promise<R>;
1230
1256
  }
1231
1257
 
@@ -1271,16 +1297,21 @@ declare function guard(cond: boolean, error: string): Result<true, string>;
1271
1297
  */
1272
1298
  declare class EventBusImpl<Evt extends DomainEvent<string, unknown>> implements EventBus<Evt> {
1273
1299
  private readonly handlers;
1274
- subscribe<T extends Evt>(eventType: string, handler: EventHandler<T>): () => void;
1300
+ subscribe<T extends Evt>(eventType: Evt["type"], handler: EventHandler<T>): () => void;
1301
+ once<T extends Evt>(eventType: Evt["type"]): Promise<T>;
1275
1302
  publish(events: ReadonlyArray<Evt>): Promise<void>;
1276
1303
  }
1277
1304
 
1305
+ declare const __specBrand: unique symbol;
1278
1306
  /**
1279
1307
  * A Specification is a named, standalone object that represents a business rule for a query.
1280
1308
  * It is "translatable" into a concrete database query.
1309
+ *
1310
+ * Uses a branded type to carry the generic parameter without requiring
1311
+ * implementors to add a runtime field.
1281
1312
  */
1282
1313
  interface ISpecification<T> {
1283
- readonly _type: T;
1314
+ readonly [__specBrand]?: T;
1284
1315
  }
1285
1316
 
1286
1317
  /**
@@ -1297,12 +1328,10 @@ interface ISpecification<T> {
1297
1328
  * Child entities cannot be loaded or saved independently - they exist only
1298
1329
  * within the aggregate boundary and are managed through the Aggregate Root.
1299
1330
  *
1300
- * @template TState - The type of the aggregate state (contains child entities and value objects)
1301
- * @template TEvent - The union type of all domain events
1302
- * @template TAgg - The aggregate root type (must be an Aggregate Root Entity)
1331
+ * @template TAgg - The aggregate root type (must implement IAggregateRoot)
1303
1332
  * @template TId - The type of the aggregate root identifier
1304
1333
  */
1305
- interface IRepository<TState, TEvent extends DomainEvent<string, unknown>, TAgg extends IAggregateRoot<TId> & Aggregate<TState, TEvent>, TId extends Id<string>> {
1334
+ interface IRepository<TAgg extends IAggregateRoot<TId>, TId extends Id<string>> {
1306
1335
  getById(id: TId): Promise<TAgg | null>;
1307
1336
  findOne(spec: ISpecification<TAgg>): Promise<TAgg | null>;
1308
1337
  find(spec: ISpecification<TAgg>): Promise<TAgg[]>;
@@ -1541,4 +1570,4 @@ declare abstract class ValueObject<T extends object> implements IValueObject<T>
1541
1570
  toJSON(): Readonly<T>;
1542
1571
  }
1543
1572
 
1544
- export { type Aggregate, type AggregateConfig, AggregateEventSourced, type AggregateEventSourcedConfig, AggregateRoot, type AggregateSnapshot, type Clock, type Command, CommandBus, type CommandHandler, type DomainEvent, Entity, type EventBus, EventBusImpl, type EventHandler, type EventMetadata, type IAggregateEventSourced, type IAggregateRoot, type ICommandBus, type IEntity, type IQueryBus, type IRepository, type ISpecification, type IValueObject, type Id, type IdGenerator, type Identifiable, 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 };
1573
+ export { type AggregateConfig, AggregateRoot, type AggregateSnapshot, type AggregateState, type Command, CommandBus, type CommandHandler, type DomainEvent, Entity, type EventBus, EventBusImpl, type EventHandler, type EventMetadata, EventSourcedAggregate, type EventSourcedAggregateConfig, type IAggregateRoot, type ICommandBus, type IEntity, type IEventSourcedAggregate, type IQueryBus, type IRepository, type ISpecification, type IValueObject, type Id, type IdGenerator, type Identifiable, 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, sameEntity, sameVersion, updateEntityById, vo, voEquals, voEqualsExcept, voWithValidation, voWithValidationUnsafe, withCommit };