@shirudo/ddd-kit 1.0.0-rc.5 → 1.0.0-rc.7

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
@@ -25,6 +25,16 @@ type Id<Tag extends string> = string & {
25
25
  * generator type — `IdGenerator<"UserId">.next()` returns `Id<"UserId">`
26
26
  * with no caller-side generic to abuse.
27
27
  *
28
+ * **Your factory must produce unique ids under concurrent calls.**
29
+ * The kit makes no attempt to dedupe or detect collisions — a collision
30
+ * silently overwrites earlier rows (under unique-key constraints) or
31
+ * silently aliases two different entities (without them). Safe choices:
32
+ * `crypto.randomUUID()` (UUIDv4, the default for events), ULID, UUIDv7,
33
+ * KSUID — all collision-resistant by design. Unsafe choices: `Date.now()`
34
+ * alone (duplicates within the same millisecond), a process-local
35
+ * counter without persistence (resets to 1 on restart, collides with
36
+ * prior runs), a sequential id derived from non-atomic state.
37
+ *
28
38
  * @example
29
39
  * ```ts
30
40
  * import { ulid } from "ulid";
@@ -71,12 +81,56 @@ type EventIdFactory = () => string;
71
81
  *
72
82
  * **Module-scoped — last setter wins.** The factory lives as a single
73
83
  * module variable; importing two libraries that both call this races on
74
- * load order. For multi-tenant request isolation (e.g. one factory per
75
- * tenant in a single Worker invocation) **prefer the per-call
76
- * `options.eventId`** instead of mutating the global. Same caveat applies
77
- * to `setClockFactory`.
84
+ * load order, and parallel test workers will see each other's factory.
85
+ * For test isolation and short-lived contexts prefer
86
+ * {@link withEventIdFactory}; for multi-tenant request isolation
87
+ * (e.g. one factory per tenant in a single Worker invocation) **prefer
88
+ * the per-call `options.eventId`** instead of mutating the global. Same
89
+ * caveat applies to `setClockFactory`.
78
90
  */
79
91
  declare function setEventIdFactory(factory: EventIdFactory): void;
92
+ /**
93
+ * Scoped variant of {@link setEventIdFactory}: installs `factory`,
94
+ * runs `fn`, then restores the previous factory in a `finally` block —
95
+ * so the restoration happens even if `fn` throws. Safe for parallel
96
+ * tests and for synchronous request handlers that need a tenant-
97
+ * specific factory without polluting the global.
98
+ *
99
+ * **Synchronous-only — enforced at runtime.** If `fn` returns a
100
+ * thenable (a `Promise` or any object with a `then` method), the
101
+ * helper throws *before* returning the value to the caller. This
102
+ * catches the async-misuse footgun where the factory would be
103
+ * restored before the awaited body of `fn` runs, leaving the awaited
104
+ * code reading the previous factory. For async scoping across `await`
105
+ * boundaries, use `AsyncLocalStorage` — out of scope for this helper;
106
+ * build it on top if you need it.
107
+ *
108
+ * Composes by nesting: an inner `withEventIdFactory` restores back to
109
+ * the outer's factory; the outer restores to the original.
110
+ *
111
+ * **When to prefer the per-call `options.eventId` instead.** If you're
112
+ * constructing a single event and want full control over its id,
113
+ * passing `{ eventId: "..." }` to `createDomainEvent` is the strongest
114
+ * isolation — it bypasses the factory mechanism entirely, no global
115
+ * mutation, no scope to manage. Reach for `withEventIdFactory` when
116
+ * the events are constructed deep inside domain methods you can't
117
+ * thread an explicit id through (typical test scenario), or when many
118
+ * events in a scope should share the same factory.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * // In a vitest test:
123
+ * it("emits deterministic ids", () => {
124
+ * withEventIdFactory(() => "evt-fixed", () => {
125
+ * const e = createDomainEvent("X", { v: 1 });
126
+ * expect(e.eventId).toBe("evt-fixed");
127
+ * });
128
+ * // Outside the callback the default crypto.randomUUID is restored,
129
+ * // even if the body had thrown.
130
+ * });
131
+ * ```
132
+ */
133
+ declare function withEventIdFactory<T>(factory: EventIdFactory, fn: () => T): T;
80
134
  /**
81
135
  * Restores the default event-id factory (`crypto.randomUUID()`).
82
136
  * Intended for use in test `afterEach` hooks.
@@ -102,8 +156,37 @@ type ClockFactory = () => Date;
102
156
  *
103
157
  * The per-call `options.occurredAt` override always wins over this
104
158
  * factory. Symmetric to `setEventIdFactory`.
159
+ *
160
+ * Module-scoped — see {@link setEventIdFactory} for the global-state
161
+ * caveats. For test isolation prefer {@link withClockFactory}; for
162
+ * multi-tenant request isolation prefer the per-call
163
+ * `options.occurredAt`.
105
164
  */
106
165
  declare function setClockFactory(factory: ClockFactory): void;
166
+ /**
167
+ * Scoped variant of {@link setClockFactory}: installs `factory`, runs
168
+ * `fn`, then restores the previous factory in a `finally` block.
169
+ * Synchronous-only — same constraints (and same runtime thenable
170
+ * guard) as {@link withEventIdFactory}.
171
+ *
172
+ * **When to prefer the per-call `options.occurredAt` instead.** Same
173
+ * trade-off as {@link withEventIdFactory}: passing `{ occurredAt }`
174
+ * to `createDomainEvent` is the strongest isolation for single-event
175
+ * cases. The scoped helper is for events constructed deep inside
176
+ * domain methods where threading an explicit timestamp is awkward.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * it("stamps events with a fixed clock", () => {
181
+ * const fixed = new Date("2026-01-01T00:00:00Z");
182
+ * withClockFactory(() => fixed, () => {
183
+ * const e = createDomainEvent("X", { v: 1 });
184
+ * expect(e.occurredAt).toEqual(fixed);
185
+ * });
186
+ * });
187
+ * ```
188
+ */
189
+ declare function withClockFactory<T>(factory: ClockFactory, fn: () => T): T;
107
190
  /**
108
191
  * Restores the default clock factory (`() => new Date()`).
109
192
  * Intended for use in test `afterEach` hooks.
@@ -189,6 +272,13 @@ interface DomainEvent<T extends string, P = void> {
189
272
  */
190
273
  metadata?: EventMetadata;
191
274
  }
275
+ /**
276
+ * Upper-bound alias for "any `DomainEvent` shape". Use as a generic
277
+ * constraint when a type parameter should accept any concrete event
278
+ * union. The `unknown` payload is the upper bound — concrete unions
279
+ * still narrow via `Extract<Evt, { type: K }>` at the use-site.
280
+ */
281
+ type AnyDomainEvent = DomainEvent<string, unknown>;
192
282
  /**
193
283
  * Shared option bag for the `createDomainEvent*` factories.
194
284
  */
@@ -263,7 +353,7 @@ declare function createDomainEventWithMetadata<T extends string, P>(type: T, pay
263
353
  * );
264
354
  * ```
265
355
  */
266
- declare function copyMetadata(sourceEvent: DomainEvent<string, unknown>, additionalMetadata?: Partial<EventMetadata>): EventMetadata;
356
+ declare function copyMetadata(sourceEvent: AnyDomainEvent, additionalMetadata?: Partial<EventMetadata>): EventMetadata;
267
357
  /**
268
358
  * Merges multiple metadata objects into one.
269
359
  * Later metadata objects override earlier ones for the same keys.
@@ -502,7 +592,7 @@ declare function sameEntity<TId extends Id<string>>(a: Identifiable<TId>, b: Ide
502
592
  * // item is { id: itemId1, productId: "prod-1", quantity: 2 }
503
593
  * ```
504
594
  */
505
- declare function findEntityById<TId extends Id<string>, T extends Identifiable<TId>>(entities: T[], id: TId): T | undefined;
595
+ declare function findEntityById<TId extends Id<string>, T extends Identifiable<TId>>(entities: ReadonlyArray<T>, id: TId): T | undefined;
506
596
  /**
507
597
  * Checks if an entity with the given ID exists in the collection.
508
598
  *
@@ -520,7 +610,7 @@ declare function findEntityById<TId extends Id<string>, T extends Identifiable<T
520
610
  * hasEntityId(items, itemId2); // false
521
611
  * ```
522
612
  */
523
- declare function hasEntityId<TId extends Id<string>, T extends Identifiable<TId>>(entities: T[], id: TId): boolean;
613
+ declare function hasEntityId<TId extends Id<string>, T extends Identifiable<TId>>(entities: ReadonlyArray<T>, id: TId): boolean;
524
614
  /**
525
615
  * Removes an entity with the given ID from the collection.
526
616
  * Returns a new array without the entity.
@@ -540,7 +630,7 @@ declare function hasEntityId<TId extends Id<string>, T extends Identifiable<TId>
540
630
  * // updated is [{ id: itemId2, productId: "prod-2", quantity: 1 }]
541
631
  * ```
542
632
  */
543
- declare function removeEntityById<TId extends Id<string>, T extends Identifiable<TId>>(entities: T[], id: TId): T[];
633
+ declare function removeEntityById<TId extends Id<string>, T extends Identifiable<TId>>(entities: ReadonlyArray<T>, id: TId): T[];
544
634
  /**
545
635
  * Updates an entity with the given ID in the collection.
546
636
  * Returns a new array with the updated entity.
@@ -564,7 +654,7 @@ declare function removeEntityById<TId extends Id<string>, T extends Identifiable
564
654
  * // updated is [{ id: itemId1, productId: "prod-1", quantity: 3 }]
565
655
  * ```
566
656
  */
567
- declare function updateEntityById<TId extends Id<string>, T extends Identifiable<TId>>(entities: T[], id: TId, updater: (entity: T) => T): T[];
657
+ declare function updateEntityById<TId extends Id<string>, T extends Identifiable<TId>>(entities: ReadonlyArray<T>, id: TId, updater: (entity: T) => T): T[];
568
658
  /**
569
659
  * Replaces an entity with the given ID in the collection.
570
660
  * Returns a new array with the replaced entity.
@@ -588,7 +678,7 @@ declare function updateEntityById<TId extends Id<string>, T extends Identifiable
588
678
  * });
589
679
  * ```
590
680
  */
591
- declare function replaceEntityById<TId extends Id<string>, T extends Identifiable<TId>>(entities: T[], id: TId, replacement: T): T[];
681
+ declare function replaceEntityById<TId extends Id<string>, T extends Identifiable<TId>>(entities: ReadonlyArray<T>, id: TId, replacement: T): T[];
592
682
  /**
593
683
  * Extracts all IDs from a collection of entities.
594
684
  *
@@ -606,7 +696,7 @@ declare function replaceEntityById<TId extends Id<string>, T extends Identifiabl
606
696
  * // ids is [itemId1, itemId2]
607
697
  * ```
608
698
  */
609
- declare function entityIds<TId extends Id<string>, T extends Identifiable<TId>>(entities: T[]): TId[];
699
+ declare function entityIds<TId extends Id<string>, T extends Identifiable<TId>>(entities: ReadonlyArray<T>): TId[];
610
700
 
611
701
  /**
612
702
  * Marker interface for Aggregate Roots.
@@ -635,7 +725,7 @@ declare function entityIds<TId extends Id<string>, T extends Identifiable<TId>>(
635
725
  * }
636
726
  * ```
637
727
  */
638
- interface IAggregateRoot<TId extends Id<string>> {
728
+ interface IAggregateRoot<TId extends Id<string>, TEvent = never> {
639
729
  /**
640
730
  * Unique identifier of the aggregate root entity.
641
731
  */
@@ -646,11 +736,25 @@ interface IAggregateRoot<TId extends Id<string>> {
646
736
  * This version applies to the entire aggregate, including all child entities.
647
737
  */
648
738
  readonly version: Version;
739
+ /**
740
+ * Read-only list of domain events recorded on this aggregate that have
741
+ * not yet been flushed to the outbox / persistence layer. Both state-
742
+ * stored (`AggregateRoot`) and event-sourced (`EventSourcedAggregate`)
743
+ * aggregates expose them under the same name, so Repository.save() can
744
+ * harvest them uniformly without branching on the aggregate flavour.
745
+ */
746
+ readonly pendingEvents: ReadonlyArray<TEvent>;
747
+ /**
748
+ * Clears the pending-event list. Called by `markPersisted` after a
749
+ * successful write — the events have been handed off to the outbox
750
+ * / event store and are no longer the aggregate's responsibility.
751
+ */
752
+ clearPendingEvents(): void;
649
753
  /**
650
754
  * Post-save hook: a `Repository.save()` implementation calls this with
651
755
  * the persisted version after a successful write to push the new
652
- * version back into the aggregate and clear any recorded domain events
653
- * (they are now safely on the write side / in the outbox).
756
+ * version back into the aggregate and clear pendingEvents (they are
757
+ * now safely on the write side / in the outbox).
654
758
  *
655
759
  * Required by the interface so a Repository implementation can call it
656
760
  * via the published `IAggregateRoot` contract without taking the
@@ -665,17 +769,14 @@ interface IAggregateRoot<TId extends Id<string>> {
665
769
  */
666
770
  interface AggregateConfig {
667
771
  /**
668
- * Whether `setState()` should bump the version automatically.
772
+ * Whether `setState()` should bump the version automatically when the
773
+ * caller omits the per-call `bumpVersion` argument.
669
774
  *
670
- * Defaults to **`false`** for `AggregateRoot` because `setState()`
671
- * already takes an explicit `bumpVersion` argument per call, so adding
672
- * an "always bump" config on top would be redundant. Keep it `false`
673
- * unless you have a subclass that never passes `bumpVersion` and you
674
- * want every state change to advance the version anyway.
675
- *
676
- * (Contrast with `EventSourcedAggregate`, which defaults this to
677
- * `true` because every event-sourced state change is per definition a
678
- * versioned commit.)
775
+ * Defaults to **`false`** — `setState()` already takes an explicit
776
+ * `bumpVersion` argument per call, so the config is just the default
777
+ * the per-call argument falls back to. Set to `true` only if you have
778
+ * a subclass that never passes `bumpVersion` and you want every state
779
+ * change to advance the version anyway.
679
780
  */
680
781
  autoVersionBump?: boolean;
681
782
  }
@@ -701,7 +802,7 @@ interface AggregateConfig {
701
802
  *
702
803
  * @template TState - The type of the aggregate state (contains child entities and value objects)
703
804
  * @template TId - The type of the aggregate root identifier
704
- * @template TEvent - The type of domain events recorded by this aggregate (defaults to unknown)
805
+ * @template TEvent - The type of domain events recorded by this aggregate. Defaults to `never` — aggregates without a declared event type cannot emit events (emitting any event becomes a compile error). Supply a concrete event union to opt in.
705
806
  *
706
807
  * @example
707
808
  * ```typescript
@@ -717,34 +818,75 @@ interface AggregateConfig {
717
818
  * }
718
819
  * ```
719
820
  */
720
- declare abstract class AggregateRoot<TState, TId extends Id<string>, TEvent = never> extends Entity<TState, TId> implements IAggregateRoot<TId> {
821
+ declare abstract class AggregateRoot<TState, TId extends Id<string>, TEvent = never> extends Entity<TState, TId> implements IAggregateRoot<TId, TEvent> {
721
822
  private _version;
722
823
  get version(): Version;
723
824
  protected setVersion(version: Version): void;
724
825
  private readonly _config;
725
826
  private readonly _autoVersionBump;
726
- private _domainEvents;
827
+ private _pendingEvents;
727
828
  /**
728
- * Returns a read-only list of domain events recorded by this aggregate.
729
- * These events are side-effects of state changes.
829
+ * Read-only list of domain events recorded on this aggregate that have
830
+ * not yet been flushed to the outbox / persistence layer.
730
831
  */
731
- get domainEvents(): ReadonlyArray<TEvent>;
832
+ get pendingEvents(): ReadonlyArray<TEvent>;
732
833
  /**
733
- * Clears the list of recorded domain events.
734
- * Call this after dispatching the events.
834
+ * Clears the pending-event list. Call this after the events have been
835
+ * dispatched (typically `markPersisted` handles it for you).
735
836
  */
736
- clearDomainEvents(): void;
837
+ clearPendingEvents(): void;
737
838
  /**
738
- * Post-save hook called by a `Repository.save()` implementation to push
739
- * the persisted version back into the in-memory aggregate and clear the
740
- * recorded domain events (they are now safely on the write side / in
741
- * the outbox).
839
+ * **Framework lifecycle method — `@sealed`.** Called by `withCommit`
840
+ * (or by your own orchestration code, after harvesting `pendingEvents`)
841
+ * to push the persisted version back into the in-memory aggregate and
842
+ * clear `pendingEvents`. TypeScript has no `final` keyword, but
843
+ * subclasses **should not** override this method directly.
844
+ *
845
+ * Overriding without calling `super.markPersisted(version)` silently
846
+ * leaks `pendingEvents` — the next `withCommit` will re-dispatch them
847
+ * through the outbox, double-emitting events. This bug has been hit
848
+ * in production by consumers; the {@link onPersisted} hook below is
849
+ * the safer extension point.
850
+ *
851
+ * If you must override (legitimate cases are very rare), call
852
+ * `super.markPersisted(version)` FIRST so the framework's cleanup
853
+ * runs, then add your logic afterwards.
742
854
  *
743
- * Use this so `save()` can keep its `Promise<void>` return type: the
744
- * caller holds the aggregate reference, which is up to date after this
745
- * call.
855
+ * @param version - The version assigned by the persistence layer
856
+ * @see onPersisted the safe extension point for subclasses
746
857
  */
747
858
  markPersisted(version: Version): void;
859
+ /**
860
+ * Subclass extension point — fires AFTER {@link markPersisted} has
861
+ * updated the version and cleared `pendingEvents`. Override this for
862
+ * post-persist logging, metrics, or cache-eviction without risk of
863
+ * breaking the framework's pendingEvents cleanup.
864
+ *
865
+ * The default implementation is a no-op. Subclasses do NOT need to
866
+ * call `super.onPersisted(version)` — there is nothing in the parent
867
+ * implementation to preserve.
868
+ *
869
+ * **`onPersisted` deliberately receives only the version, not the
870
+ * drained events.** Event-driven post-persist logic (aggregate-level
871
+ * audit logging, per-event-type side effects) belongs in `EventBus`
872
+ * subscribers or the outbox dispatcher — that is the proper
873
+ * Aggregate-Boundary separation. Building event-aware logic into
874
+ * `onPersisted` couples aggregate lifecycle to event processing and
875
+ * recreates the boundary problems Vernon's aggregate discipline is
876
+ * meant to prevent.
877
+ *
878
+ * **The hook must return synchronously.** `markPersisted` is `void`-
879
+ * typed and calls `onPersisted` without `await`. TypeScript's
880
+ * permissive `void` will accept an `async`-override returning
881
+ * `Promise<void>`, but the returned promise is fire-and-forget —
882
+ * any rejection becomes an unhandled rejection and `withCommit`
883
+ * proceeds without waiting. For asynchronous work, subscribe to the
884
+ * relevant domain event on the `EventBus` instead; that is the
885
+ * properly awaited extension point.
886
+ *
887
+ * @param version - The version that was just persisted
888
+ */
889
+ protected onPersisted(_version: Version): void;
748
890
  /**
749
891
  * Mutates state and records the resulting domain events in the
750
892
  * **canonical record-after-mutation order**. Use this instead of calling
@@ -972,11 +1114,7 @@ declare class ConcurrencyConflictError extends InfrastructureError<"ConcurrencyC
972
1114
  * @template TId - The type of the aggregate root identifier
973
1115
  * @template TEvent - The union type of all domain events
974
1116
  */
975
- interface IEventSourcedAggregate<TId extends Id<string>, TEvent extends DomainEvent<string, unknown>> extends IAggregateRoot<TId> {
976
- /**
977
- * Returns a read-only list of new, not-yet-persisted events.
978
- */
979
- readonly pendingEvents: ReadonlyArray<TEvent>;
1117
+ interface IEventSourcedAggregate<TId extends Id<string>, TEvent extends AnyDomainEvent> extends IAggregateRoot<TId, TEvent> {
980
1118
  /**
981
1119
  * Reconstitutes the aggregate from an event history. Returns `Result`
982
1120
  * because event-stream corruption is an expected recoverable failure
@@ -984,45 +1122,9 @@ interface IEventSourcedAggregate<TId extends Id<string>, TEvent extends DomainEv
984
1122
  *
985
1123
  * @param history - An ordered list of past events
986
1124
  */
987
- loadFromHistory(history: TEvent[]): Result<void, DomainError>;
988
- /**
989
- * Clears the list of pending events.
990
- */
991
- clearPendingEvents(): void;
992
- /**
993
- * Checks if the aggregate has any pending events.
994
- */
995
- hasPendingEvents(): boolean;
996
- /**
997
- * Returns the number of pending events.
998
- */
999
- getEventCount(): number;
1000
- /**
1001
- * Returns the latest pending event, if any.
1002
- */
1003
- getLatestEvent(): TEvent | undefined;
1004
- }
1005
- type Handler<TState, TEvent> = (state: TState, event: TEvent) => TState;
1006
- /**
1007
- * Configuration options for EventSourcedAggregate behavior.
1008
- */
1009
- interface EventSourcedAggregateConfig {
1010
- /**
1011
- * Whether `apply()` should bump the version per event.
1012
- *
1013
- * Defaults to **`true`** for `EventSourcedAggregate` — each applied
1014
- * event is by definition a versioned state change, so the canonical
1015
- * event-sourcing pattern is "one event = one version bump". Set to
1016
- * `false` only if your event store assigns version numbers itself
1017
- * and you want the aggregate to track them via `bumpVersion()` /
1018
- * `setVersion()` calls instead.
1019
- *
1020
- * (Contrast with `AggregateRoot`, which defaults this to `false`
1021
- * because its `setState()` already takes a per-call `bumpVersion`
1022
- * argument.)
1023
- */
1024
- autoVersionBump?: boolean;
1125
+ loadFromHistory(history: ReadonlyArray<TEvent>): Result<void, DomainError>;
1025
1126
  }
1127
+ type Handler<TState, TEvent extends AnyDomainEvent> = (state: TState, event: TEvent) => TState;
1026
1128
  /**
1027
1129
  * Base class for Event-Sourced Aggregate Roots (Vernon, IDDD Chapter 8).
1028
1130
  *
@@ -1072,22 +1174,66 @@ interface EventSourcedAggregateConfig {
1072
1174
  * }
1073
1175
  * ```
1074
1176
  */
1075
- declare abstract class EventSourcedAggregate<TState, TEvent extends DomainEvent<string, unknown>, TId extends Id<string>> extends Entity<TState, TId> implements IEventSourcedAggregate<TId, TEvent> {
1177
+ declare abstract class EventSourcedAggregate<TState, TEvent extends AnyDomainEvent, TId extends Id<string>> extends Entity<TState, TId> implements IEventSourcedAggregate<TId, TEvent> {
1076
1178
  private _version;
1077
1179
  get version(): Version;
1078
1180
  private setVersion;
1079
1181
  private _pendingEvents;
1080
- private readonly _autoVersionBump;
1081
1182
  get pendingEvents(): ReadonlyArray<TEvent>;
1082
1183
  clearPendingEvents(): void;
1083
1184
  /**
1084
- * Post-save hook called by a `Repository.save()` implementation to push
1085
- * the persisted version back into the in-memory aggregate and clear the
1086
- * pending events (they are now in the event store / outbox). Lets
1087
- * `save()` keep its `Promise<void>` return type.
1185
+ * **Framework lifecycle method — `@sealed`.** Called by `withCommit`
1186
+ * (or by your own orchestration code, after harvesting `pendingEvents`)
1187
+ * to push the persisted version back into the in-memory aggregate and
1188
+ * clear `pendingEvents`. TypeScript has no `final` keyword, but
1189
+ * subclasses **should not** override this method directly.
1190
+ *
1191
+ * Overriding without calling `super.markPersisted(version)` silently
1192
+ * leaks `pendingEvents` — the next `withCommit` will re-dispatch them
1193
+ * through the outbox, double-emitting events. This bug has been hit
1194
+ * in production by consumers; the {@link onPersisted} hook below is
1195
+ * the safer extension point.
1196
+ *
1197
+ * If you must override (legitimate cases are very rare), call
1198
+ * `super.markPersisted(version)` FIRST so the framework's cleanup
1199
+ * runs, then add your logic afterwards.
1200
+ *
1201
+ * @param version - The version assigned by the persistence layer
1202
+ * @see onPersisted — the safe extension point for subclasses
1088
1203
  */
1089
1204
  markPersisted(version: Version): void;
1090
- protected constructor(id: TId, initialState: TState, config?: EventSourcedAggregateConfig);
1205
+ /**
1206
+ * Subclass extension point — fires AFTER {@link markPersisted} has
1207
+ * updated the version and cleared `pendingEvents`. Override this for
1208
+ * post-persist logging, metrics, or cache-eviction without risk of
1209
+ * breaking the framework's pendingEvents cleanup.
1210
+ *
1211
+ * The default implementation is a no-op. Subclasses do NOT need to
1212
+ * call `super.onPersisted(version)` — there is nothing in the parent
1213
+ * implementation to preserve.
1214
+ *
1215
+ * **`onPersisted` deliberately receives only the version, not the
1216
+ * drained events.** Event-driven post-persist logic (aggregate-level
1217
+ * audit logging, per-event-type side effects) belongs in `EventBus`
1218
+ * subscribers or the outbox dispatcher — that is the proper
1219
+ * Aggregate-Boundary separation. Building event-aware logic into
1220
+ * `onPersisted` couples aggregate lifecycle to event processing and
1221
+ * recreates the boundary problems Vernon's aggregate discipline is
1222
+ * meant to prevent.
1223
+ *
1224
+ * **The hook must return synchronously.** `markPersisted` is `void`-
1225
+ * typed and calls `onPersisted` without `await`. TypeScript's
1226
+ * permissive `void` will accept an `async`-override returning
1227
+ * `Promise<void>`, but the returned promise is fire-and-forget —
1228
+ * any rejection becomes an unhandled rejection and `withCommit`
1229
+ * proceeds without waiting. For asynchronous work, subscribe to the
1230
+ * relevant domain event on the `EventBus` instead; that is the
1231
+ * properly awaited extension point.
1232
+ *
1233
+ * @param version - The version that was just persisted
1234
+ */
1235
+ protected onPersisted(_version: Version): void;
1236
+ protected constructor(id: TId, initialState: TState);
1091
1237
  /**
1092
1238
  * Validates an event before it is applied. Default is no-op.
1093
1239
  * Subclasses override to throw a concrete `DomainError` subclass when
@@ -1123,11 +1269,6 @@ declare abstract class EventSourcedAggregate<TState, TEvent extends DomainEvent<
1123
1269
  * resolved via the (statically-sound) `handlers` map.
1124
1270
  */
1125
1271
  private dispatchAndCommit;
1126
- /**
1127
- * Manually bumps the aggregate version.
1128
- * Only needed if `autoVersionBump` is disabled.
1129
- */
1130
- protected bumpVersion(): void;
1131
1272
  /**
1132
1273
  * Reconstitutes the aggregate from an event history. Catches `DomainError`
1133
1274
  * thrown during replay and returns it as an `Err` — this is the
@@ -1139,10 +1280,7 @@ declare abstract class EventSourcedAggregate<TState, TEvent extends DomainEvent<
1139
1280
  * an aggregate already at v=1 (e.g. after a creation event) loading
1140
1281
  * 2 events ends at v=3, not v=2.
1141
1282
  */
1142
- loadFromHistory(history: TEvent[]): Result<void, DomainError>;
1143
- hasPendingEvents(): boolean;
1144
- getEventCount(): number;
1145
- getLatestEvent(): TEvent | undefined;
1283
+ loadFromHistory(history: ReadonlyArray<TEvent>): Result<void, DomainError>;
1146
1284
  /**
1147
1285
  * Creates a snapshot of the current aggregate state.
1148
1286
  */
@@ -1157,7 +1295,7 @@ declare abstract class EventSourcedAggregate<TState, TEvent extends DomainEvent<
1157
1295
  * aggregate is rolled back to its pre-call state + version. Partial
1158
1296
  * restoration is never observable to the caller.
1159
1297
  */
1160
- restoreFromSnapshotWithEvents(snapshot: AggregateSnapshot<TState>, eventsAfterSnapshot: TEvent[]): Result<void, DomainError>;
1298
+ restoreFromSnapshotWithEvents(snapshot: AggregateSnapshot<TState>, eventsAfterSnapshot: ReadonlyArray<TEvent>): Result<void, DomainError>;
1161
1299
  /**
1162
1300
  * A map of event types to their corresponding handlers.
1163
1301
  * Subclasses MUST implement this property.
@@ -1456,9 +1594,7 @@ type EventHandler<Evt> = (event: Evt) => Promise<void> | void;
1456
1594
  * await bus.publish([orderCreatedEvent, orderShippedEvent]);
1457
1595
  * ```
1458
1596
  */
1459
- interface EventBus<Evt extends {
1460
- type: string;
1461
- }> {
1597
+ interface EventBus<Evt extends AnyDomainEvent> {
1462
1598
  /**
1463
1599
  * Publishes events to all subscribed handlers.
1464
1600
  *
@@ -1551,7 +1687,7 @@ interface OnceOptions {
1551
1687
  * own `eventId`, generate its own UUID, use the row's auto-increment
1552
1688
  * primary key, or whatever the storage layer prefers.
1553
1689
  */
1554
- interface OutboxRecord<Evt> {
1690
+ interface OutboxRecord<Evt extends AnyDomainEvent> {
1555
1691
  dispatchId: string;
1556
1692
  event: Evt;
1557
1693
  }
@@ -1571,7 +1707,7 @@ interface OutboxRecord<Evt> {
1571
1707
  * that's already marked is a no-op, not an error. This lets the
1572
1708
  * dispatcher safely retry on partial-failure.
1573
1709
  */
1574
- interface Outbox<Evt> {
1710
+ interface Outbox<Evt extends AnyDomainEvent> {
1575
1711
  /**
1576
1712
  * Persists events. Called from inside `withCommit`'s transactional
1577
1713
  * callback, atomically with the aggregate write.
@@ -1607,11 +1743,15 @@ interface Outbox<Evt> {
1607
1743
  * and rolls back if it throws.
1608
1744
  *
1609
1745
  * `TCtx` is the persistence layer's transaction handle — Drizzle's `tx`,
1610
- * Prisma's `tx`, Mongo's session, or `unknown` for the no-context path.
1611
- * The scope opens the transaction and passes the handle to `fn`; the
1612
- * use case binds its repositories to that handle (typically by
1613
- * constructing a tx-scoped repo from the ctx). Default `TCtx = unknown`
1614
- * keeps the no-context callers compiling.
1746
+ * Prisma's `tx`, Mongo's session, etc. The scope opens the transaction
1747
+ * and passes the handle to `fn`; the use case binds its repositories to
1748
+ * that handle (typically by constructing a tx-scoped repo from the ctx).
1749
+ *
1750
+ * No default for `TCtx`: every implementor names their context type
1751
+ * explicitly. For genuinely context-free scopes (in-memory tests, naive
1752
+ * no-tx scopes) use `TransactionScope<undefined>` — that's a conscious
1753
+ * "there is nothing meaningful here" statement, not an accidental
1754
+ * `unknown` fallback.
1615
1755
  *
1616
1756
  * Intentionally **not** Fowler's full Unit of Work (no change tracking,
1617
1757
  * no `registerDirty` / `registerNew` / `registerDeleted`, no commit-time
@@ -1631,11 +1771,11 @@ interface Outbox<Evt> {
1631
1771
  * ```typescript
1632
1772
  * await scope.transactional(async (tx) => {
1633
1773
  * // Construct tx-bound repos from ctx (your factory / DI of choice)
1634
- * const orders = makeOrderRepo(tx);
1774
+ * const orderRepository = makeOrderRepository(tx);
1635
1775
  *
1636
- * const order = await orders.getByIdOrFail(orderId);
1776
+ * const order = await orderRepository.getByIdOrFail(orderId);
1637
1777
  * order.confirm();
1638
- * await orders.save(order);
1778
+ * await orderRepository.save(order);
1639
1779
  * });
1640
1780
  * ```
1641
1781
  *
@@ -1645,60 +1785,98 @@ interface Outbox<Evt> {
1645
1785
  * (constructor injection, factory functions, `withTx` chains); pick one
1646
1786
  * and keep it consistent.
1647
1787
  */
1648
- interface TransactionScope<TCtx = unknown> {
1788
+ interface TransactionScope<TCtx> {
1649
1789
  transactional<T>(fn: (ctx: TCtx) => Promise<T>): Promise<T>;
1650
1790
  }
1651
1791
 
1652
1792
  /**
1653
1793
  * Helper for executing a write Use Case inside a transaction scope.
1654
1794
  *
1795
+ * The use-case callback returns the aggregates it touched; `withCommit`
1796
+ * owns the post-save lifecycle (harvest, outbox, mark-persisted, publish).
1797
+ * This matches the Vernon / Axon / EventFlow unit-of-work pattern:
1798
+ * `Repository.save` is pure persistence; "this aggregate has been
1799
+ * committed" is the orchestrator's call to make, not the repo's.
1800
+ *
1655
1801
  * Order of operations:
1656
1802
  * 1. `fn(ctx)` runs inside `scope.transactional(...)` — domain mutations
1657
1803
  * + repo writes happen here. `ctx` is whatever transaction handle the
1658
1804
  * `scope` exposes (Drizzle `tx`, Prisma `tx`, Mongo session, or
1659
- * `unknown` for the no-context path).
1660
- * 2. `outbox.add(events)` is also inside the transaction (skipped when
1661
- * the use case emits no events), so events persist atomically with
1662
- * the state change.
1805
+ * `undefined` for context-free scopes).
1806
+ * 2. **Still inside the transaction**, `withCommit` harvests every
1807
+ * aggregate's `pendingEvents` and writes them via `outbox.add` (so
1808
+ * events persist atomically with the state change). Skipped when no
1809
+ * events were recorded.
1810
+ *
1811
+ * **Harvest order.** Events are concatenated in the order
1812
+ * aggregates appear in the returned `aggregates` array, then in
1813
+ * each aggregate's `pendingEvents` order (insertion order via
1814
+ * `apply` / `commit` / `addDomainEvent`). So `aggregates: [a, b]`
1815
+ * with `a` emitting `[e1, e2]` and `b` emitting `[e3]` produces
1816
+ * `outbox.add([e1, e2, e3])` and `bus.publish([e1, e2, e3])` in
1817
+ * that exact order.
1818
+ *
1819
+ * **Two ordering guarantees, not one.** Within a single aggregate
1820
+ * the order is *causal* — events are recorded in the order the
1821
+ * domain methods ran, and subscribers (handlers, projections,
1822
+ * replay) MUST process them in that order. Across aggregates the
1823
+ * order in this batch is deterministic but *not* a domain
1824
+ * guarantee. Greg Young / Vernon IDDD §10: aggregates are
1825
+ * independent consistency boundaries; events across them are
1826
+ * eventually consistent. Subscribers should NOT engineer
1827
+ * dependencies on cross-aggregate ordering — use
1828
+ * `EventMetadata.causationId` to express true causation, or a
1829
+ * process manager to coordinate. The in-process EventBus delivers
1830
+ * this batch in order, sequential outbox-dispatchers preserve it
1831
+ * too, but parallel dispatchers or message brokers may reorder
1832
+ * across aggregates at delivery time.
1663
1833
  * 3. The transaction commits.
1664
- * 4. **After** the commit, `bus.publish(events)` fires for the
1665
- * in-process fast path (also skipped when the event list is empty).
1834
+ * 4. **After** the commit, `aggregate.markPersisted(aggregate.version)`
1835
+ * fires on each returned aggregate only now are pending events
1836
+ * considered flushed.
1837
+ * 5. `bus.publish(events)` fires for the in-process fast path (skipped
1838
+ * when no events or no `bus` is wired).
1666
1839
  *
1667
1840
  * Publishing AFTER commit prevents the classic "publish before commit"
1668
1841
  * footgun: in-process subscribers can never react to events from a
1669
- * transaction that later rolled back. If `bus.publish` itself fails, the
1842
+ * transaction that later rolled back. If `bus.publish` itself throws, the
1670
1843
  * outbox still holds the events and an outbox-dispatcher will deliver
1671
1844
  * them (eventual consistency).
1672
1845
  *
1673
- * @example No-context (tests / single-store flows)
1674
- * ```typescript
1675
- * const result = await withCommit({ outbox, bus, scope }, async () => {
1676
- * order.confirm();
1677
- * await orderRepo.save(order);
1678
- * return { result: order.id, events: order.domainEvents };
1679
- * });
1680
- * ```
1846
+ * If the transaction rolls back, `markPersisted` is **not** called — the
1847
+ * aggregate keeps its pending events, so the caller can retry or discard.
1848
+ *
1849
+ * **Duplicate aggregates are deduped by reference.** If the returned
1850
+ * `aggregates` array contains the same instance twice — e.g. a use
1851
+ * case touches an order via two repository references that happen to
1852
+ * resolve to the same identity-map entry — `withCommit` dedupes by
1853
+ * JavaScript object identity before harvesting. Each event lands in
1854
+ * the outbox exactly once and `markPersisted` fires exactly once. Two
1855
+ * *different* instances with the same logical id cannot be detected
1856
+ * at this layer; that is a Repository contract violation (failure to
1857
+ * maintain Fowler's Identity Map per Unit of Work). See
1858
+ * `docs/guide/repository.md` → "Identity Map: one instance per
1859
+ * aggregate per Unit of Work" for the requirement on `IRepository`
1860
+ * implementations that makes this dedupe sound.
1681
1861
  *
1682
1862
  * @example Tx-bound repos (Drizzle, Prisma, Mongo, …)
1683
1863
  * ```typescript
1684
1864
  * const result = await withCommit({ outbox, bus, scope }, async (tx) => {
1685
- * const orders = makeOrderRepo(tx); // your factory binds tx to the repo
1686
- * const order = await orders.getByIdOrFail(orderId);
1865
+ * const orderRepository = makeOrderRepository(tx); // your factory binds tx to the repo
1866
+ * const order = await orderRepository.getByIdOrFail(orderId);
1687
1867
  * order.confirm();
1688
- * await orders.save(order);
1689
- * return { result: order.id, events: order.domainEvents };
1868
+ * await orderRepository.save(order); // pure persistence — does NOT call markPersisted
1869
+ * return { result: order.id, aggregates: [order] };
1690
1870
  * });
1691
1871
  * ```
1692
1872
  */
1693
- declare function withCommit<Evt extends {
1694
- type: string;
1695
- }, R, TCtx = unknown>(deps: {
1873
+ declare function withCommit<Evt extends AnyDomainEvent, R, TCtx>(deps: {
1696
1874
  outbox: Outbox<Evt>;
1697
1875
  bus?: EventBus<Evt>;
1698
1876
  scope: TransactionScope<TCtx>;
1699
1877
  }, fn: (ctx: TCtx) => Promise<{
1700
1878
  result: R;
1701
- events: ReadonlyArray<Evt>;
1879
+ aggregates: ReadonlyArray<IAggregateRoot<Id<string>, Evt>>;
1702
1880
  }>): Promise<R>;
1703
1881
 
1704
1882
  /**
@@ -1933,7 +2111,7 @@ declare class QueryBus<TMap extends QueryTypeMap = QueryTypeMap> implements IQue
1933
2111
  * // Both handlers will be called
1934
2112
  * ```
1935
2113
  */
1936
- declare class EventBusImpl<Evt extends DomainEvent<string, unknown>> implements EventBus<Evt> {
2114
+ declare class EventBusImpl<Evt extends AnyDomainEvent> implements EventBus<Evt> {
1937
2115
  private readonly handlers;
1938
2116
  subscribe<K extends Evt["type"]>(eventType: K, handler: EventHandler<Extract<Evt, {
1939
2117
  type: K;
@@ -1952,6 +2130,44 @@ declare class EventBusImpl<Evt extends DomainEvent<string, unknown>> implements
1952
2130
  publish(events: ReadonlyArray<Evt>): Promise<void>;
1953
2131
  }
1954
2132
 
2133
+ /**
2134
+ * In-memory reference implementation of `Outbox<Evt>`.
2135
+ *
2136
+ * Intended for tests, single-process workers, and quick-start demos.
2137
+ * Uses the event's own `eventId` as the dispatch id — the common, clean
2138
+ * choice. Storage is a `Map<string, OutboxRecord<Evt>>` keyed by
2139
+ * `eventId`, so re-adding the same event is naturally idempotent (the
2140
+ * duplicate entry overwrites itself; `getPending` returns each event at
2141
+ * most once).
2142
+ *
2143
+ * For production, back the outbox with a transactional store so the
2144
+ * outbox row participates in the same transaction as the aggregate
2145
+ * write (see `TransactionScope` + `withCommit`). This class lives in
2146
+ * memory only — events are lost on process restart.
2147
+ *
2148
+ * @example
2149
+ * ```ts
2150
+ * import { InMemoryOutbox, EventBusImpl, withCommit } from "@shirudo/ddd-kit";
2151
+ *
2152
+ * const outbox = new InMemoryOutbox<OrderEvent>();
2153
+ * const bus = new EventBusImpl<OrderEvent>();
2154
+ *
2155
+ * await withCommit({ scope, outbox, bus }, async (tx) => {
2156
+ * const orderRepository = makeOrderRepository(tx);
2157
+ * const order = await orderRepository.getByIdOrFail(id);
2158
+ * order.confirm();
2159
+ * await orderRepository.save(order);
2160
+ * return { result: order.id, aggregates: [order] };
2161
+ * });
2162
+ * ```
2163
+ */
2164
+ declare class InMemoryOutbox<Evt extends AnyDomainEvent> implements Outbox<Evt> {
2165
+ private readonly pending;
2166
+ add(events: ReadonlyArray<Evt>): Promise<void>;
2167
+ getPending(limit?: number): Promise<ReadonlyArray<OutboxRecord<Evt>>>;
2168
+ markDispatched(dispatchIds: ReadonlyArray<string>): Promise<void>;
2169
+ }
2170
+
1955
2171
  /**
1956
2172
  * Core repository contract for Aggregate Roots.
1957
2173
  *
@@ -1988,21 +2204,88 @@ interface IRepository<TAgg extends IAggregateRoot<TId>, TId extends Id<string>>
1988
2204
  */
1989
2205
  exists(id: TId): Promise<boolean>;
1990
2206
  /**
1991
- * Persists the aggregate (insert or update). Implementations should:
2207
+ * Persists the aggregate (insert or update). Implementations are
2208
+ * responsible for **persistence only** — they must NOT touch the
2209
+ * aggregate's in-memory state:
1992
2210
  *
1993
2211
  * 1. Throw `ConcurrencyConflictError` from `@shirudo/ddd-kit` when the
1994
2212
  * aggregate's expected version does not match the version currently
1995
2213
  * stored (optimistic concurrency).
1996
- * 2. After a successful write, call `aggregate.markPersisted(newVersion)`
1997
- * so the in-memory aggregate reflects the new version and clears its
1998
- * pending/domain events.
1999
- *
2000
- * Return type stays `void` the caller already holds the aggregate
2001
- * reference, which is now up to date.
2214
+ * 2. Write the aggregate to durable storage.
2215
+ *
2216
+ * **Insert vs update — library convention.** A fresh aggregate begins
2217
+ * at `version === 0` (the `Version` brand defaults to `0` in both
2218
+ * `AggregateRoot` and `EventSourcedAggregate`). After the first
2219
+ * versioned mutation (`setState(_, true)`, `apply()`, `commit()`) the
2220
+ * version is `> 0`. Implementations distinguish the two paths by the
2221
+ * incoming `aggregate.version`:
2222
+ *
2223
+ * - `aggregate.version === 0` → **INSERT** (no existing row to lock
2224
+ * against; the write succeeds unconditionally or fails the unique
2225
+ * constraint on `id`).
2226
+ * - `aggregate.version > 0` → **UPDATE** with the OCC predicate
2227
+ * `WHERE id = ? AND version = expected`. If the row count is `0`,
2228
+ * another writer raced you — throw `ConcurrencyConflictError`.
2229
+ *
2230
+ * The library does not formalise this in the type system because
2231
+ * version-bump semantics differ across the two aggregate flavours
2232
+ * (state-stored aggregates bump on the user's call to `setState(_,
2233
+ * true)`; event-sourced aggregates bump on every `apply()` by
2234
+ * definition). The `version === 0` invariant for "never persisted" is
2235
+ * the common contract.
2236
+ *
2237
+ * Do **not** call `aggregate.markPersisted(...)` here. The library's
2238
+ * `withCommit` orchestrator handles the post-save lifecycle (harvest
2239
+ * pending events into the outbox, then mark persisted after commit).
2240
+ * Calling `markPersisted` inside `save` clears pending events too early
2241
+ * and breaks the harvest path — and is also why the Vernon/Axon/
2242
+ * EventFlow pattern separates persistence from commit-events.
2243
+ *
2244
+ * If you are not using `withCommit` (custom orchestration), call
2245
+ * `aggregate.markPersisted(aggregate.version)` yourself **after** you
2246
+ * have harvested `aggregate.pendingEvents` for downstream dispatch.
2002
2247
  */
2003
2248
  save(aggregate: TAgg): Promise<void>;
2004
2249
  /**
2005
- * Removes the aggregate by id.
2250
+ * Removes the aggregate's row by id. Pure persistence — does NOT
2251
+ * harvest pending events from the aggregate (the contract takes
2252
+ * only the id, so there is no aggregate to harvest from).
2253
+ *
2254
+ * Before reaching for `delete`, ask whether the user-facing "delete"
2255
+ * is the right domain verb. Most are actually state transitions
2256
+ * (*cancel*, *archive*, *close*, *deactivate*, *terminate*) with
2257
+ * proper domain names that should be modelled as state changes plus
2258
+ * a recorded event — not as row removal.
2259
+ *
2260
+ * `delete(id)` belongs in the toolkit for three distinct cases, in
2261
+ * decreasing order of common occurrence (see
2262
+ * `docs/guide/repository.md` → "Deletion and Domain Events" for
2263
+ * worked examples):
2264
+ *
2265
+ * 1. **State transition that records an event.** The user-facing
2266
+ * "delete" maps to a real domain operation (e.g. `order.cancel()`,
2267
+ * `order.archive()`). Call `save(aggregate)`; the row stays with
2268
+ * a status column. `delete(id)` is never called by the use case.
2269
+ *
2270
+ * 2. **Hard-delete with event harvest.** The row genuinely must
2271
+ * vanish (regulatory purge, retention-window expiry, true
2272
+ * termination) *and* the disappearance is a domain fact
2273
+ * subscribers care about. Inside `withCommit`'s transactional
2274
+ * callback, record the deletion event on the aggregate, then
2275
+ * call `delete(id)`. Return the aggregate in the `aggregates`
2276
+ * array so `withCommit` harvests its pending events into the
2277
+ * outbox before the row is gone.
2278
+ *
2279
+ * 3. **Hard-delete without event.** Deletion is invisible to the
2280
+ * domain (abandoned-cart cleanup, expired session rows). No
2281
+ * subscriber cares. If the entity has identity in the ubiquitous
2282
+ * language, you probably want path 1 or 2 instead.
2283
+ *
2284
+ * In pure event-sourced systems `delete` is rarely meaningful —
2285
+ * end-of-lifecycle there is a `Closed` / `Terminated` event in the
2286
+ * stream, and identity persists in the event log. `delete` applies
2287
+ * primarily to state-stored aggregates and snapshot / projection
2288
+ * tables.
2006
2289
  */
2007
2290
  delete(id: TId): Promise<void>;
2008
2291
  }
@@ -2276,4 +2559,4 @@ declare abstract class ValueObject<T extends object> implements IValueObject<T>
2276
2559
  toJSON(): Readonly<T>;
2277
2560
  }
2278
2561
 
2279
- export { type AggregateConfig, AggregateNotFoundError, AggregateRoot, type AggregateSnapshot, type ClockFactory, type Command, CommandBus, type CommandHandler, ConcurrencyConflictError, type CreateDomainEventOptions, DeepEqualExceptOptions, DomainError, type DomainEvent, Entity, type EventBus, EventBusImpl, type EventHandler, type EventIdFactory, type EventMetadata, EventSourcedAggregate, type EventSourcedAggregateConfig, type IAggregateRoot, type ICommandBus, type IEntity, type IEventSourcedAggregate, type IQueryBus, type IQueryableRepository, type IRepository, type IValueObject, type Id, type IdGenerator, type Identifiable, InfrastructureError, MissingHandlerError, type OnceOptions, type Outbox, type OutboxRecord, type Query, QueryBus, type QueryHandler, type TransactionScope, type VO, ValueObject, type Version, copyMetadata, createDomainEvent, createDomainEventWithMetadata, deepFreeze, entityIds, findEntityById, freezeShallow, hasEntityId, mergeMetadata, removeEntityById, replaceEntityById, resetClockFactory, resetEventIdFactory, sameEntity, sameVersion, setClockFactory, setEventIdFactory, updateEntityById, vo, voEquals, voEqualsExcept, voWithValidation, withCommit };
2562
+ export { type AggregateConfig, AggregateNotFoundError, AggregateRoot, type AggregateSnapshot, type AnyDomainEvent, type ClockFactory, type Command, CommandBus, type CommandHandler, ConcurrencyConflictError, type CreateDomainEventOptions, DeepEqualExceptOptions, DomainError, type DomainEvent, Entity, type EventBus, EventBusImpl, type EventHandler, type EventIdFactory, type EventMetadata, EventSourcedAggregate, type IAggregateRoot, type ICommandBus, type IEntity, type IEventSourcedAggregate, type IQueryBus, type IQueryableRepository, type IRepository, type IValueObject, type Id, type IdGenerator, type Identifiable, InMemoryOutbox, InfrastructureError, MissingHandlerError, type OnceOptions, type Outbox, type OutboxRecord, type Query, QueryBus, type QueryHandler, type TransactionScope, type VO, ValueObject, type Version, copyMetadata, createDomainEvent, createDomainEventWithMetadata, deepFreeze, entityIds, findEntityById, freezeShallow, hasEntityId, mergeMetadata, removeEntityById, replaceEntityById, resetClockFactory, resetEventIdFactory, sameEntity, sameVersion, setClockFactory, setEventIdFactory, updateEntityById, vo, voEquals, voEqualsExcept, voWithValidation, withClockFactory, withCommit, withEventIdFactory };