@shirudo/ddd-kit 1.0.0-rc.4 → 1.0.0-rc.6

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.js CHANGED
@@ -571,26 +571,26 @@ var AggregateRoot = class extends Entity {
571
571
  }
572
572
  _config;
573
573
  _autoVersionBump;
574
- _domainEvents = [];
574
+ _pendingEvents = [];
575
575
  /**
576
- * Returns a read-only list of domain events recorded by this aggregate.
577
- * These events are side-effects of state changes.
576
+ * Read-only list of domain events recorded on this aggregate that have
577
+ * not yet been flushed to the outbox / persistence layer.
578
578
  */
579
- get domainEvents() {
580
- return Object.freeze(this._domainEvents.slice());
579
+ get pendingEvents() {
580
+ return Object.freeze(this._pendingEvents.slice());
581
581
  }
582
582
  /**
583
- * Clears the list of recorded domain events.
584
- * Call this after dispatching the events.
583
+ * Clears the pending-event list. Call this after the events have been
584
+ * dispatched (typically `markPersisted` handles it for you).
585
585
  */
586
- clearDomainEvents() {
587
- this._domainEvents = [];
586
+ clearPendingEvents() {
587
+ this._pendingEvents = [];
588
588
  }
589
589
  /**
590
590
  * Post-save hook called by a `Repository.save()` implementation to push
591
- * the persisted version back into the in-memory aggregate and clear the
592
- * recorded domain events (they are now safely on the write side / in
593
- * the outbox).
591
+ * the persisted version back into the in-memory aggregate and clear
592
+ * pendingEvents (they are now safely on the write side / in the
593
+ * outbox).
594
594
  *
595
595
  * Use this so `save()` can keep its `Promise<void>` return type: the
596
596
  * caller holds the aggregate reference, which is up to date after this
@@ -598,7 +598,7 @@ var AggregateRoot = class extends Entity {
598
598
  */
599
599
  markPersisted(version) {
600
600
  this.setVersion(version);
601
- this._domainEvents = [];
601
+ this._pendingEvents = [];
602
602
  }
603
603
  /**
604
604
  * Mutates state and records the resulting domain events in the
@@ -687,7 +687,7 @@ var AggregateRoot = class extends Entity {
687
687
  * @param event - The domain event to record
688
688
  */
689
689
  addDomainEvent(event) {
690
- this._domainEvents.push(event);
690
+ this._pendingEvents.push(event);
691
691
  }
692
692
  /**
693
693
  * Manually bumps the aggregate version.
@@ -749,8 +749,9 @@ var AggregateRoot = class extends Entity {
749
749
  * ```
750
750
  */
751
751
  restoreFromSnapshot(snapshot) {
752
- this.validateState(snapshot.state);
753
- this._state = freezeShallow(snapshot.state);
752
+ const cloned = structuredClone(snapshot.state);
753
+ this.validateState(cloned);
754
+ this._state = freezeShallow(cloned);
754
755
  this.setVersion(snapshot.version);
755
756
  }
756
757
  };
@@ -765,8 +766,8 @@ var InfrastructureError = class extends BaseError {
765
766
  }
766
767
  };
767
768
  var MissingHandlerError = class extends BaseError {
768
- constructor(eventType) {
769
- super(`Missing handler for event type: ${eventType}`);
769
+ constructor(eventType, cause) {
770
+ super(`Missing handler for event type: ${eventType}`, cause);
770
771
  this.eventType = eventType;
771
772
  }
772
773
  static {
@@ -774,12 +775,12 @@ var MissingHandlerError = class extends BaseError {
774
775
  }
775
776
  };
776
777
  var AggregateNotFoundError = class extends InfrastructureError {
777
- constructor(aggregateType, id) {
778
- super(`Aggregate not found: ${aggregateType}(${id})`);
778
+ constructor(aggregateType, id, cause) {
779
+ super(`Aggregate not found: ${aggregateType}(${id})`, cause);
779
780
  this.aggregateType = aggregateType;
780
781
  this.id = id;
781
782
  this.withUserMessage(
782
- `The requested ${aggregateType.toLowerCase()} could not be found.`
783
+ `The requested ${aggregateType} could not be found.`
783
784
  );
784
785
  }
785
786
  static {
@@ -787,9 +788,10 @@ var AggregateNotFoundError = class extends InfrastructureError {
787
788
  }
788
789
  };
789
790
  var ConcurrencyConflictError = class extends InfrastructureError {
790
- constructor(aggregateType, aggregateId, expectedVersion, actualVersion) {
791
+ constructor(aggregateType, aggregateId, expectedVersion, actualVersion, cause) {
791
792
  super(
792
- `Concurrency conflict on ${aggregateType}(${aggregateId}): expected version ${expectedVersion}, actual ${actualVersion}`
793
+ `Concurrency conflict on ${aggregateType}(${aggregateId}): expected version ${expectedVersion}, actual ${actualVersion}`,
794
+ cause
793
795
  );
794
796
  this.aggregateType = aggregateType;
795
797
  this.aggregateId = aggregateId;
@@ -825,7 +827,6 @@ var EventSourcedAggregate = class extends Entity {
825
827
  }
826
828
  // --- Event tracking ---
827
829
  _pendingEvents = [];
828
- _autoVersionBump;
829
830
  get pendingEvents() {
830
831
  return Object.freeze(this._pendingEvents.slice());
831
832
  }
@@ -842,9 +843,8 @@ var EventSourcedAggregate = class extends Entity {
842
843
  this.setVersion(version);
843
844
  this._pendingEvents = [];
844
845
  }
845
- constructor(id, initialState, config) {
846
+ constructor(id, initialState) {
846
847
  super(id, initialState);
847
- this._autoVersionBump = config?.autoVersionBump ?? true;
848
848
  }
849
849
  // --- Event application ---
850
850
  /**
@@ -892,18 +892,9 @@ var EventSourcedAggregate = class extends Entity {
892
892
  this._state = freezeShallow(nextState);
893
893
  if (isNew) {
894
894
  this._pendingEvents.push(event);
895
- if (this._autoVersionBump) {
896
- this.setVersion(this._version + 1);
897
- }
895
+ this.setVersion(this._version + 1);
898
896
  }
899
897
  }
900
- /**
901
- * Manually bumps the aggregate version.
902
- * Only needed if `autoVersionBump` is disabled.
903
- */
904
- bumpVersion() {
905
- this.setVersion(this._version + 1);
906
- }
907
898
  // --- History & Snapshots ---
908
899
  /**
909
900
  * Reconstitutes the aggregate from an event history. Catches `DomainError`
@@ -929,15 +920,6 @@ var EventSourcedAggregate = class extends Entity {
929
920
  this.setVersion(startVersion + history.length);
930
921
  return ok();
931
922
  }
932
- hasPendingEvents() {
933
- return this._pendingEvents.length > 0;
934
- }
935
- getEventCount() {
936
- return this._pendingEvents.length;
937
- }
938
- getLatestEvent() {
939
- return this._pendingEvents[this._pendingEvents.length - 1];
940
- }
941
923
  /**
942
924
  * Creates a snapshot of the current aggregate state.
943
925
  */
@@ -961,7 +943,7 @@ var EventSourcedAggregate = class extends Entity {
961
943
  restoreFromSnapshotWithEvents(snapshot, eventsAfterSnapshot) {
962
944
  const previousState = this._state;
963
945
  const previousVersion = this._version;
964
- this._state = freezeShallow(snapshot.state);
946
+ this._state = freezeShallow(structuredClone(snapshot.state));
965
947
  this.setVersion(snapshot.version);
966
948
  for (const event of eventsAfterSnapshot) {
967
949
  try {
@@ -981,10 +963,9 @@ var CommandBus = class {
981
963
  static {
982
964
  __name(this, "CommandBus");
983
965
  }
984
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
985
966
  handlers = /* @__PURE__ */ new Map();
986
967
  register(commandType, handler) {
987
- this.handlers.set(commandType, handler);
968
+ this.handlers.set(commandType, (cmd) => handler(cmd));
988
969
  }
989
970
  async execute(command) {
990
971
  const handler = this.handlers.get(command.type);
@@ -1003,12 +984,22 @@ var CommandBus = class {
1003
984
 
1004
985
  // src/app/handler.ts
1005
986
  async function withCommit(deps, fn) {
1006
- const { result, events } = await deps.scope.transactional(async () => {
1007
- const fnResult = await fn();
1008
- await deps.outbox.add(fnResult.events);
1009
- return fnResult;
1010
- });
1011
- if (deps.bus) {
987
+ const { result, aggregates, events } = await deps.scope.transactional(
988
+ async (ctx) => {
989
+ const fnResult = await fn(ctx);
990
+ const harvested = fnResult.aggregates.flatMap(
991
+ (agg) => agg.pendingEvents
992
+ );
993
+ if (harvested.length > 0) {
994
+ await deps.outbox.add(harvested);
995
+ }
996
+ return { ...fnResult, events: harvested };
997
+ }
998
+ );
999
+ for (const agg of aggregates) {
1000
+ agg.markPersisted(agg.version);
1001
+ }
1002
+ if (deps.bus && events.length > 0) {
1012
1003
  await deps.bus.publish(events);
1013
1004
  }
1014
1005
  return result;
@@ -1018,10 +1009,9 @@ var QueryBus = class {
1018
1009
  static {
1019
1010
  __name(this, "QueryBus");
1020
1011
  }
1021
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1022
1012
  handlers = /* @__PURE__ */ new Map();
1023
1013
  register(queryType, handler) {
1024
- this.handlers.set(queryType, handler);
1014
+ this.handlers.set(queryType, (query) => handler(query));
1025
1015
  }
1026
1016
  async execute(query) {
1027
1017
  const handler = this.handlers.get(query.type);
@@ -1042,7 +1032,7 @@ var QueryBus = class {
1042
1032
  if (!handler) {
1043
1033
  throw new Error(`No handler registered for query type: ${query.type}`);
1044
1034
  }
1045
- return handler(query);
1035
+ return await handler(query);
1046
1036
  }
1047
1037
  };
1048
1038
 
@@ -1051,7 +1041,6 @@ var EventBusImpl = class {
1051
1041
  static {
1052
1042
  __name(this, "EventBusImpl");
1053
1043
  }
1054
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1055
1044
  handlers = /* @__PURE__ */ new Map();
1056
1045
  subscribe(eventType, handler) {
1057
1046
  const type = eventType;
@@ -1151,6 +1140,29 @@ var EventBusImpl = class {
1151
1140
  }
1152
1141
  };
1153
1142
 
1154
- export { AggregateNotFoundError, AggregateRoot, CommandBus, ConcurrencyConflictError, DomainError, Entity, EventBusImpl, EventSourcedAggregate, InfrastructureError, MissingHandlerError, QueryBus, ValueObject, copyMetadata, createDomainEvent, createDomainEventWithMetadata, deepEqual, deepEqualExcept, deepFreeze, deepOmit, entityIds, findEntityById, freezeShallow, hasEntityId, mergeMetadata, removeEntityById, replaceEntityById, resetClockFactory, resetEventIdFactory, sameEntity, sameVersion, setClockFactory, setEventIdFactory, updateEntityById, vo, voEquals, voEqualsExcept, voWithValidation, withCommit };
1143
+ // src/events/outbox.ts
1144
+ var InMemoryOutbox = class {
1145
+ static {
1146
+ __name(this, "InMemoryOutbox");
1147
+ }
1148
+ pending = /* @__PURE__ */ new Map();
1149
+ async add(events) {
1150
+ for (const event of events) {
1151
+ this.pending.set(event.eventId, {
1152
+ dispatchId: event.eventId,
1153
+ event
1154
+ });
1155
+ }
1156
+ }
1157
+ async getPending(limit) {
1158
+ const all = [...this.pending.values()];
1159
+ return typeof limit === "number" ? all.slice(0, limit) : all;
1160
+ }
1161
+ async markDispatched(dispatchIds) {
1162
+ for (const id of dispatchIds) this.pending.delete(id);
1163
+ }
1164
+ };
1165
+
1166
+ export { AggregateNotFoundError, AggregateRoot, CommandBus, ConcurrencyConflictError, DomainError, Entity, EventBusImpl, EventSourcedAggregate, InMemoryOutbox, InfrastructureError, MissingHandlerError, QueryBus, ValueObject, copyMetadata, createDomainEvent, createDomainEventWithMetadata, deepEqual, deepEqualExcept, deepFreeze, deepOmit, entityIds, findEntityById, freezeShallow, hasEntityId, mergeMetadata, removeEntityById, replaceEntityById, resetClockFactory, resetEventIdFactory, sameEntity, sameVersion, setClockFactory, setEventIdFactory, updateEntityById, vo, voEquals, voEqualsExcept, voWithValidation, withCommit };
1155
1167
  //# sourceMappingURL=index.js.map
1156
1168
  //# sourceMappingURL=index.js.map