@shirudo/ddd-kit 0.16.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,33 +424,24 @@ 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
@@ -333,14 +456,15 @@ interface AggregateConfig {
333
456
  * }
334
457
  *
335
458
  * confirm(): void {
336
- * this._state = { ...this._state, status: "confirmed" };
337
- * this.bumpVersion(); // Versions the entire aggregate
459
+ * this.setState({ ...this.state, status: "confirmed" }, true);
338
460
  * }
339
461
  * }
340
462
  * ```
341
463
  */
342
464
  declare abstract class AggregateRoot<TState, TId extends Id<string>, TEvent = unknown> extends Entity<TState, TId> implements IAggregateRoot<TId> {
343
- version: Version;
465
+ private _version;
466
+ get version(): Version;
467
+ protected setVersion(version: Version): void;
344
468
  private readonly _config;
345
469
  private readonly _autoVersionBump;
346
470
  private _domainEvents;
@@ -417,7 +541,7 @@ declare abstract class AggregateRoot<TState, TId extends Id<string>, TEvent = un
417
541
  * @template TId - The type of the aggregate root identifier
418
542
  * @template TEvent - The union type of all domain events
419
543
  */
420
- 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> {
421
545
  /**
422
546
  * Returns a read-only list of new, not-yet-persisted events.
423
547
  */
@@ -447,9 +571,9 @@ interface IAggregateEventSourced<TId extends Id<string>, TEvent extends DomainEv
447
571
  }
448
572
  type Handler<TState, TEvent> = (state: TState, event: TEvent) => TState;
449
573
  /**
450
- * Extended configuration options for AggregateEventSourced behavior.
574
+ * Configuration options for EventSourcedAggregate behavior.
451
575
  */
452
- interface AggregateEventSourcedConfig extends AggregateConfig {
576
+ interface EventSourcedAggregateConfig {
453
577
  /**
454
578
  * Whether to automatically bump the version when applying new events.
455
579
  * Defaults to true. Set to false to manually control versioning.
@@ -457,26 +581,15 @@ interface AggregateEventSourcedConfig extends AggregateConfig {
457
581
  autoVersionBump?: boolean;
458
582
  }
459
583
  /**
460
- * Base class for Event-Sourced Aggregate Roots (Entities).
461
- *
462
- * Extends `AggregateRoot` to create an Aggregate Root Entity with Event Sourcing capabilities.
463
- * The Aggregate Root is the parent Entity of the aggregate and represents it externally.
464
- *
465
- * The aggregate state (`TState`) contains:
466
- * - Child entities (Entities with id, but no own version)
467
- * - Value objects (immutable objects)
584
+ * Base class for Event-Sourced Aggregate Roots (Vernon, IDDD Chapter 8).
468
585
  *
469
- * All changes to child entities are versioned through the Aggregate Root. The version
470
- * applies to the entire aggregate, including all child entities.
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.
471
589
  *
472
- * Extends `AggregateRoot` with Event Sourcing capabilities:
473
- * - Event tracking (pendingEvents)
474
- * - Event handlers for state transitions
475
- * - Event validation
476
- * - History replay
477
- *
478
- * Use this class when you want Event Sourcing with full event tracking
479
- * 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.
480
593
  *
481
594
  * @template TState - The type of the aggregate state (contains child entities and value objects)
482
595
  * @template TEvent - The union type of all domain events
@@ -484,8 +597,7 @@ interface AggregateEventSourcedConfig extends AggregateConfig {
484
597
  *
485
598
  * @example
486
599
  * ```typescript
487
- * // Order is an Aggregate Root (an Entity) with Event Sourcing
488
- * class Order extends AggregateEventSourced<OrderState, OrderEvent, OrderId> {
600
+ * class Order extends EventSourcedAggregate<OrderState, OrderEvent, OrderId> {
489
601
  * confirm(): void {
490
602
  * this.apply(createDomainEvent("OrderConfirmed", {}));
491
603
  * }
@@ -499,58 +611,32 @@ interface AggregateEventSourcedConfig extends AggregateConfig {
499
611
  * }
500
612
  * ```
501
613
  */
502
- declare abstract class AggregateEventSourced<TState, TEvent extends DomainEvent<string, unknown>, TId extends Id<string>> extends AggregateRoot<TState, TId, TEvent> implements IAggregateEventSourced<TId, TEvent> {
503
- private readonly _eventConfig;
504
- private readonly _eventAutoVersionBump;
505
- protected constructor(id: TId, initialState: TState, config?: AggregateEventSourcedConfig);
506
- /**
507
- * Returns a read-only list of new, not-yet-persisted events.
508
- */
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;
509
620
  get pendingEvents(): ReadonlyArray<TEvent>;
510
- /**
511
- * Clears the list of pending events.
512
- * Typically called after the events have been persisted.
513
- */
514
621
  clearPendingEvents(): void;
622
+ protected constructor(id: TId, initialState: TState, config?: EventSourcedAggregateConfig);
515
623
  /**
516
624
  * Validates an event before it is applied.
517
625
  * Override this method to add custom validation logic.
518
626
  * Return `ok(true)` if the event is valid, `err(message)` otherwise.
519
- *
520
- * @param event - The event to validate
521
- * @returns Result indicating if the event is valid
522
- *
523
- * @example
524
- * ```typescript
525
- * protected validateEvent(event: OrderEvent): Result<true, string> {
526
- * if (event.type === "OrderShipped" && this.state.status !== "confirmed") {
527
- * return err("Order must be confirmed before shipping");
528
- * }
529
- * return ok(true);
530
- * }
531
- * ```
532
627
  */
533
628
  protected validateEvent(_event: TEvent): Result<true, string>;
534
629
  /**
535
- * Applies an event to change the state and adds it
536
- * to the list of pending events.
630
+ * Applies an event to change the state and adds it to pending events.
537
631
  * Returns a Result type instead of throwing an error.
538
632
  *
539
633
  * @param event - The domain event to apply
540
- * @param isNew - Indicates whether the event is new (and needs to be persisted)
541
- * or if it is being loaded from history
542
- * @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
543
635
  */
544
636
  protected apply(event: TEvent, isNew?: boolean): Result<void, string>;
545
637
  /**
546
- * Applies an event to change the state and adds it
547
- * to the list of pending events.
638
+ * Applies an event to change the state and adds it to pending events.
548
639
  * Throws an error if validation fails or handler is missing.
549
- *
550
- * @param event - The domain event to apply
551
- * @param isNew - Indicates whether the event is new (and needs to be persisted)
552
- * or if it is being loaded from history
553
- * @throws Error if event validation fails or handler is missing
554
640
  */
555
641
  protected applyUnsafe(event: TEvent, isNew?: boolean): void;
556
642
  /**
@@ -561,41 +647,17 @@ declare abstract class AggregateEventSourced<TState, TEvent extends DomainEvent<
561
647
  /**
562
648
  * Reconstitutes the aggregate from an event history.
563
649
  * Sets the version to the number of events in the history.
564
- *
565
- * @param history - An ordered list of past events
566
650
  */
567
651
  loadFromHistory(history: TEvent[]): Result<void, string>;
568
- /**
569
- * Checks if the aggregate has any pending events.
570
- *
571
- * @returns true if there are pending events, false otherwise
572
- */
573
652
  hasPendingEvents(): boolean;
574
- /**
575
- * Returns the number of pending events.
576
- *
577
- * @returns The count of pending events
578
- */
579
653
  getEventCount(): number;
654
+ getLatestEvent(): TEvent | undefined;
580
655
  /**
581
- * Returns the latest pending event, if any.
582
- *
583
- * @returns The most recent event or undefined if no events exist
656
+ * Creates a snapshot of the current aggregate state.
584
657
  */
585
- getLatestEvent(): TEvent | undefined;
658
+ createSnapshot(): AggregateSnapshot<TState>;
586
659
  /**
587
- * Restores the aggregate from a snapshot and applies events that occurred after the snapshot.
588
- * This is more efficient than replaying all events from the beginning.
589
- *
590
- * @param snapshot - The snapshot to restore from
591
- * @param eventsAfterSnapshot - Events that occurred after the snapshot was taken
592
- *
593
- * @example
594
- * ```typescript
595
- * const snapshot = await snapshotRepository.getLatest(aggregateId);
596
- * const eventsAfter = await eventStore.getEventsAfter(aggregateId, snapshot.version);
597
- * aggregate.restoreFromSnapshotWithEvents(snapshot, eventsAfter);
598
- * ```
660
+ * Restores the aggregate from a snapshot and applies events that occurred after.
599
661
  */
600
662
  restoreFromSnapshotWithEvents(snapshot: AggregateSnapshot<TState>, eventsAfterSnapshot: TEvent[]): Result<void, string>;
601
663
  /**
@@ -613,170 +675,32 @@ type Version = number & {
613
675
  readonly __v: true;
614
676
  };
615
677
  /**
616
- * Metadata associated with a domain event for traceability and correlation.
617
- * Used in event-driven architectures to track event flow across services.
618
- */
619
- interface EventMetadata {
620
- /**
621
- * Correlation ID for tracing events across multiple services/components.
622
- * Typically used to group related events in a distributed system.
623
- */
624
- correlationId?: string;
625
- /**
626
- * Causation ID referencing the event or command that caused this event.
627
- * Used to build event chains and understand causality.
628
- */
629
- causationId?: string;
630
- /**
631
- * User ID of the person or system that triggered the event.
632
- */
633
- userId?: string;
634
- /**
635
- * Source service or component that produced the event.
636
- */
637
- source?: string;
638
- /**
639
- * Additional custom metadata fields.
640
- * Allows extensibility for domain-specific metadata.
641
- */
642
- [key: string]: unknown;
643
- }
644
- /**
645
- * Domain Event represents something meaningful that happened in the domain.
646
- * 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).
647
680
  *
648
- * @template T - The event type name (e.g., "OrderCreated")
649
- * @template P - The event payload type
650
- */
651
- interface DomainEvent<T extends string, P = void> {
652
- /**
653
- * The type of the event, used for routing and handling.
654
- */
655
- type: T;
656
- /**
657
- * The event payload containing the domain data.
658
- * Omitted when P is void (events without payload).
659
- */
660
- payload: P;
661
- /**
662
- * Timestamp when the event occurred.
663
- */
664
- occurredAt: Date;
665
- /**
666
- * Event schema version for handling schema evolution.
667
- * Defaults to 1 if not specified. Higher versions indicate schema changes.
668
- */
669
- version?: number;
670
- /**
671
- * Optional metadata for traceability, correlation, and auditing.
672
- * Includes correlationId, causationId, userId, source, and custom fields.
673
- */
674
- metadata?: EventMetadata;
675
- }
676
-
677
- /**
678
- * Structural interface representing an aggregate with state and events.
679
- * 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.
680
683
  *
681
684
  * @template State - The type of the aggregate state
682
- * @template Evt - The union type of all domain events
683
685
  */
684
- interface Aggregate<State, Evt extends DomainEvent<string, unknown>> {
686
+ interface AggregateState<State> {
685
687
  state: Readonly<State>;
686
688
  version: Version;
687
- pendingEvents: ReadonlyArray<Evt>;
688
689
  }
689
- declare function aggregate<State, Evt extends DomainEvent<string, unknown>>(state: State, version?: Version): Aggregate<State, Evt>;
690
- declare function withEvent<S, E extends DomainEvent<string, unknown>>(agg: Aggregate<S, E>, evt: E): Aggregate<S, E>;
691
- declare function bump<S, E extends DomainEvent<string, unknown>>(agg: Aggregate<S, E>): Aggregate<S, E>;
692
- /**
693
- * Creates a domain event with default values.
694
- * Sets occurredAt to current date and version to 1 if not provided.
695
- *
696
- * @param type - The event type
697
- * @param payload - The event payload
698
- * @param options - Optional event configuration
699
- * @returns A domain event
700
- *
701
- * @example
702
- * ```typescript
703
- * const event = createDomainEvent("OrderCreated", { orderId: "123" });
704
- * ```
705
- */
706
- declare function createDomainEvent<T extends string>(type: T, payload?: undefined, options?: {
707
- occurredAt?: Date;
708
- version?: number;
709
- metadata?: EventMetadata;
710
- }): DomainEvent<T, void>;
711
- declare function createDomainEvent<T extends string, P>(type: T, payload: P, options?: {
712
- occurredAt?: Date;
713
- version?: number;
714
- metadata?: EventMetadata;
715
- }): DomainEvent<T, P>;
716
- /**
717
- * Creates a domain event with metadata for traceability.
718
- * Convenience function for creating events with correlation and causation IDs.
719
- *
720
- * @param type - The event type
721
- * @param payload - The event payload
722
- * @param metadata - Event metadata for traceability
723
- * @param options - Optional event configuration
724
- * @returns A domain event with metadata
725
- *
726
- * @example
727
- * ```typescript
728
- * const event = createDomainEventWithMetadata(
729
- * "OrderCreated",
730
- * { orderId: "123" },
731
- * {
732
- * correlationId: "corr-123",
733
- * causationId: "cmd-456",
734
- * userId: "user-789"
735
- * }
736
- * );
737
- * ```
738
- */
739
- declare function createDomainEventWithMetadata<T extends string, P>(type: T, payload: P, metadata: EventMetadata, options?: {
740
- occurredAt?: Date;
741
- version?: number;
742
- }): DomainEvent<T, P>;
743
690
  /**
744
- * Copies metadata from a source event to a new event.
745
- * Useful for maintaining correlation chains in event-driven architectures.
746
- *
747
- * @param sourceEvent - The source event to copy metadata from
748
- * @param additionalMetadata - Additional metadata to merge in
749
- * @returns Event metadata with copied and merged values
691
+ * Creates a lightweight functional aggregate state.
750
692
  *
751
693
  * @example
752
694
  * ```typescript
753
- * const newEvent = createDomainEvent(
754
- * "OrderShipped",
755
- * { orderId: "123" },
756
- * {
757
- * metadata: copyMetadata(previousEvent, { causationId: previousEvent.type })
758
- * }
759
- * );
695
+ * const order = aggregate<OrderState>({ status: "draft", items: [] });
760
696
  * ```
761
697
  */
762
- declare function copyMetadata(sourceEvent: DomainEvent<string, unknown>, additionalMetadata?: Partial<EventMetadata>): EventMetadata;
698
+ declare function aggregate<State>(state: State, version?: Version): AggregateState<State>;
763
699
  /**
764
- * Merges multiple metadata objects into one.
765
- * Later metadata objects override earlier ones for the same keys.
766
- *
767
- * @param metadataObjects - Array of metadata objects to merge
768
- * @returns Merged event metadata
769
- *
770
- * @example
771
- * ```typescript
772
- * const metadata = mergeMetadata(
773
- * { correlationId: "corr-123" },
774
- * { userId: "user-456" },
775
- * { source: "order-service" }
776
- * );
777
- * ```
700
+ * Bumps the version of a functional aggregate state.
701
+ * Returns a new aggregate state with incremented version.
778
702
  */
779
- declare function mergeMetadata(...metadataObjects: Array<EventMetadata | undefined>): EventMetadata;
703
+ declare function bump<S>(agg: AggregateState<S>): AggregateState<S>;
780
704
  /**
781
705
  * Snapshot of an aggregate state at a specific point in time.
782
706
  * Used for optimizing event replay by starting from a snapshot
@@ -799,25 +723,24 @@ interface AggregateSnapshot<TState> {
799
723
  snapshotAt: Date;
800
724
  }
801
725
  /**
802
- * 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).
803
727
  * Useful for optimistic concurrency control checks.
804
728
  *
805
- * @param a - First aggregate
806
- * @param b - Second aggregate
807
- * @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.
808
731
  *
809
732
  * @example
810
733
  * ```typescript
811
- * const aggregate1 = await repository.getById(id);
734
+ * const before = await repository.getById(id);
812
735
  * // ... some operations ...
813
- * const aggregate2 = await repository.getById(id);
736
+ * const after = await repository.getById(id);
814
737
  *
815
- * if (!sameAggregate(aggregate1, aggregate2)) {
738
+ * if (!sameVersion(before, after)) {
816
739
  * throw new Error("Aggregate was modified by another process");
817
740
  * }
818
741
  * ```
819
742
  */
820
- declare function sameAggregate<TId extends Id<string>>(a: {
743
+ declare function sameVersion<TId extends Id<string>>(a: {
821
744
  id: TId;
822
745
  version: Version;
823
746
  }, b: {
@@ -913,29 +836,58 @@ interface Command {
913
836
  */
914
837
  type CommandHandler<C extends Command, R> = (cmd: C) => Promise<Result<R, string>>;
915
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>;
916
856
  /**
917
857
  * Command Bus interface for dispatching commands to their handlers.
918
858
  * Provides a centralized way to execute commands with handler registration.
919
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
+ *
920
865
  * @example
921
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
922
874
  * const bus = new CommandBus();
923
875
  * bus.register("CreateOrder", createOrderHandler);
924
- *
925
- * const result = await bus.execute({
926
- * type: "CreateOrder",
927
- * customerId: "123",
928
- * items: [...]
929
- * });
876
+ * const result = await bus.execute({ type: "CreateOrder", ... });
877
+ * // result: Result<unknown, string>
930
878
  * ```
931
879
  */
932
- interface ICommandBus {
880
+ interface ICommandBus<TMap extends CommandTypeMap = CommandTypeMap> {
933
881
  /**
934
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.
935
884
  *
936
885
  * @param command - The command to execute
937
886
  * @returns Result containing the success value or error message
938
887
  */
888
+ execute<C extends Command & {
889
+ type: keyof TMap & string;
890
+ }>(command: C): Promise<Result<TMap[C["type"]], string>>;
939
891
  execute<C extends Command, R>(command: C): Promise<Result<R, string>>;
940
892
  /**
941
893
  * Registers a handler for a specific command type.
@@ -949,6 +901,10 @@ interface ICommandBus {
949
901
  * Simple in-memory command bus implementation.
950
902
  * Handlers are stored in a Map and dispatched based on command type.
951
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
+ *
952
908
  * **Note:** This is a basic implementation suitable for development and simple use cases.
953
909
  * For production environments, consider implementing or using a more feature-rich bus that includes:
954
910
  * - Middleware/Pipeline support (logging, validation, authorization)
@@ -961,20 +917,28 @@ interface ICommandBus {
961
917
  * The `CommandHandler` type can still be used with external production-grade buses
962
918
  * (e.g., RabbitMQ, AWS SQS) while maintaining type safety.
963
919
  *
920
+ * @template TMap - Optional mapping from command type strings to return types
921
+ *
964
922
  * @example
965
923
  * ```typescript
966
- * const bus = new CommandBus();
967
- * bus.register("CreateOrder", async (cmd) => {
968
- * // ... handler logic
969
- * return ok(orderId);
970
- * });
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>
971
929
  *
930
+ * // Without type map – same as before
931
+ * const bus = new CommandBus();
932
+ * bus.register("CreateOrder", async (cmd) => ok(orderId));
972
933
  * const result = await bus.execute({ type: "CreateOrder", ... });
973
934
  * ```
974
935
  */
975
- declare class CommandBus implements ICommandBus {
936
+ declare class CommandBus<TMap extends CommandTypeMap = CommandTypeMap> implements ICommandBus<TMap> {
976
937
  private readonly handlers;
977
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>>;
978
942
  execute<C extends Command, R>(command: C): Promise<Result<R, string>>;
979
943
  }
980
944
 
@@ -1007,7 +971,9 @@ type EventHandler<Evt> = (event: Evt) => Promise<void> | void;
1007
971
  * await bus.publish([orderCreatedEvent, orderShippedEvent]);
1008
972
  * ```
1009
973
  */
1010
- interface EventBus<Evt> {
974
+ interface EventBus<Evt extends {
975
+ type: string;
976
+ }> {
1011
977
  /**
1012
978
  * Publishes events to all subscribed handlers.
1013
979
  * All handlers for each event type will be called.
@@ -1033,7 +999,7 @@ interface EventBus<Evt> {
1033
999
  * unsubscribe();
1034
1000
  * ```
1035
1001
  */
1036
- subscribe: <T extends Evt>(eventType: string, handler: EventHandler<T>) => () => void;
1002
+ subscribe: <T extends Evt>(eventType: Evt["type"], handler: EventHandler<T>) => () => void;
1037
1003
  /**
1038
1004
  * Subscribes to the next occurrence of an event type.
1039
1005
  * Returns a Promise that resolves with the event data.
@@ -1048,14 +1014,11 @@ interface EventBus<Evt> {
1048
1014
  * console.log("Order created:", event.payload.orderId);
1049
1015
  * ```
1050
1016
  */
1051
- once: <T extends Evt>(eventType: string) => Promise<T>;
1017
+ once: <T extends Evt>(eventType: Evt["type"]) => Promise<T>;
1052
1018
  }
1053
1019
  interface Outbox<Evt> {
1054
1020
  add: (events: ReadonlyArray<Evt>) => Promise<void>;
1055
1021
  }
1056
- interface Clock {
1057
- now: () => Date;
1058
- }
1059
1022
 
1060
1023
  interface UnitOfWork {
1061
1024
  transactional<T>(fn: () => Promise<T>): Promise<T>;
@@ -1085,7 +1048,9 @@ type RepoProvider<R> = (uow: UnitOfWork) => R;
1085
1048
  * );
1086
1049
  * ```
1087
1050
  */
1088
- declare function withCommit<Evt, R>(deps: {
1051
+ declare function withCommit<Evt extends {
1052
+ type: string;
1053
+ }, R>(deps: {
1089
1054
  outbox: Outbox<Evt>;
1090
1055
  bus?: EventBus<Evt>;
1091
1056
  uow: UnitOfWork;
@@ -1169,29 +1134,57 @@ interface Query {
1169
1134
  */
1170
1135
  type QueryHandler<Q extends Query, R> = (query: Q) => Promise<R>;
1171
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>;
1172
1154
  /**
1173
1155
  * Query Bus interface for dispatching queries to their handlers.
1174
1156
  * Provides a centralized way to execute queries with handler registration.
1175
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
+ *
1176
1163
  * @example
1177
1164
  * ```typescript
1178
- * const bus = new QueryBus();
1179
- * 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>
1180
1170
  *
1181
- * const order = await bus.execute({
1182
- * type: "GetOrder",
1183
- * orderId: "123"
1184
- * });
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>
1185
1175
  * ```
1186
1176
  */
1187
- interface IQueryBus {
1177
+ interface IQueryBus<TMap extends QueryTypeMap = QueryTypeMap> {
1188
1178
  /**
1189
1179
  * Executes a query by dispatching it to the registered handler.
1190
- * 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.
1191
1181
  *
1192
1182
  * @param query - The query to execute
1193
- * @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
1194
1184
  */
1185
+ execute<Q extends Query & {
1186
+ type: keyof TMap & string;
1187
+ }>(query: Q): Promise<Result<TMap[Q["type"]], string>>;
1195
1188
  execute<Q extends Query, R>(query: Q): Promise<Result<R, string>>;
1196
1189
  /**
1197
1190
  * Executes a query by dispatching it to the registered handler.
@@ -1201,6 +1194,9 @@ interface IQueryBus {
1201
1194
  * @returns The query result
1202
1195
  * @throws Error if no handler is registered for the query type
1203
1196
  */
1197
+ executeUnsafe<Q extends Query & {
1198
+ type: keyof TMap & string;
1199
+ }>(query: Q): Promise<TMap[Q["type"]]>;
1204
1200
  executeUnsafe<Q extends Query, R>(query: Q): Promise<R>;
1205
1201
  /**
1206
1202
  * Registers a handler for a specific query type.
@@ -1210,15 +1206,14 @@ interface IQueryBus {
1210
1206
  */
1211
1207
  register<Q extends Query, R>(queryType: Q["type"], handler: QueryHandler<Q, R>): void;
1212
1208
  }
1213
- /**
1214
- * Type map for query types to their return types.
1215
- * Used to improve type inference in QueryBus.
1216
- */
1217
- type QueryTypeMap = Record<string, unknown>;
1218
1209
  /**
1219
1210
  * Simple in-memory query bus implementation.
1220
1211
  * Handlers are stored in a Map and dispatched based on query type.
1221
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
+ *
1222
1217
  * **Note:** This is a basic implementation suitable for development and simple use cases.
1223
1218
  * For production environments, consider implementing or using a more feature-rich bus that includes:
1224
1219
  * - Middleware/Pipeline support (logging, caching, rate limiting)
@@ -1231,23 +1226,32 @@ type QueryTypeMap = Record<string, unknown>;
1231
1226
  * The `QueryHandler` type can still be used with external production-grade buses
1232
1227
  * (e.g., RabbitMQ, AWS SQS) while maintaining type safety.
1233
1228
  *
1229
+ * @template TMap - Optional mapping from query type strings to return types
1230
+ *
1234
1231
  * @example
1235
1232
  * ```typescript
1236
- * const bus = new QueryBus();
1237
- * bus.register("GetOrder", async (query) => {
1238
- * return await repository.getById(query.orderId);
1239
- * });
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>
1240
1238
  *
1241
- * 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" });
1242
1243
  * ```
1243
1244
  */
1244
- declare class QueryBus<TMap extends QueryTypeMap = QueryTypeMap> implements IQueryBus {
1245
+ declare class QueryBus<TMap extends QueryTypeMap = QueryTypeMap> implements IQueryBus<TMap> {
1245
1246
  private readonly handlers;
1246
1247
  register<Q extends Query, R>(queryType: Q["type"], handler: QueryHandler<Q, R>): void;
1247
1248
  execute<Q extends Query & {
1248
- type: keyof TMap;
1249
+ type: keyof TMap & string;
1249
1250
  }>(query: Q): Promise<Result<TMap[Q["type"]], string>>;
1250
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"]]>;
1251
1255
  executeUnsafe<Q extends Query, R>(query: Q): Promise<R>;
1252
1256
  }
1253
1257
 
@@ -1293,17 +1297,21 @@ declare function guard(cond: boolean, error: string): Result<true, string>;
1293
1297
  */
1294
1298
  declare class EventBusImpl<Evt extends DomainEvent<string, unknown>> implements EventBus<Evt> {
1295
1299
  private readonly handlers;
1296
- subscribe<T extends Evt>(eventType: string, handler: EventHandler<T>): () => void;
1297
- once<T extends Evt>(eventType: string): Promise<T>;
1300
+ subscribe<T extends Evt>(eventType: Evt["type"], handler: EventHandler<T>): () => void;
1301
+ once<T extends Evt>(eventType: Evt["type"]): Promise<T>;
1298
1302
  publish(events: ReadonlyArray<Evt>): Promise<void>;
1299
1303
  }
1300
1304
 
1305
+ declare const __specBrand: unique symbol;
1301
1306
  /**
1302
1307
  * A Specification is a named, standalone object that represents a business rule for a query.
1303
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.
1304
1312
  */
1305
1313
  interface ISpecification<T> {
1306
- readonly _type: T;
1314
+ readonly [__specBrand]?: T;
1307
1315
  }
1308
1316
 
1309
1317
  /**
@@ -1320,12 +1328,10 @@ interface ISpecification<T> {
1320
1328
  * Child entities cannot be loaded or saved independently - they exist only
1321
1329
  * within the aggregate boundary and are managed through the Aggregate Root.
1322
1330
  *
1323
- * @template TState - The type of the aggregate state (contains child entities and value objects)
1324
- * @template TEvent - The union type of all domain events
1325
- * @template TAgg - The aggregate root type (must be an Aggregate Root Entity)
1331
+ * @template TAgg - The aggregate root type (must implement IAggregateRoot)
1326
1332
  * @template TId - The type of the aggregate root identifier
1327
1333
  */
1328
- 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>> {
1329
1335
  getById(id: TId): Promise<TAgg | null>;
1330
1336
  findOne(spec: ISpecification<TAgg>): Promise<TAgg | null>;
1331
1337
  find(spec: ISpecification<TAgg>): Promise<TAgg[]>;
@@ -1564,4 +1570,4 @@ declare abstract class ValueObject<T extends object> implements IValueObject<T>
1564
1570
  toJSON(): Readonly<T>;
1565
1571
  }
1566
1572
 
1567
- 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 };