@shirudo/ddd-kit 1.0.0-rc.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/dist/http.d.ts +46 -0
- package/dist/http.js +18 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +516 -605
- package/dist/index.js +160 -263
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +28 -28
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { err, ok } from '@shirudo/result';
|
|
2
|
-
import { BaseError } from '@shirudo/base-error';
|
|
2
|
+
import { BaseError, ValidationError } from '@shirudo/base-error';
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
@@ -589,35 +589,83 @@ function entityIds(entities) {
|
|
|
589
589
|
}
|
|
590
590
|
__name(entityIds, "entityIds");
|
|
591
591
|
|
|
592
|
-
// src/aggregate/aggregate
|
|
593
|
-
var
|
|
592
|
+
// src/aggregate/base-aggregate.ts
|
|
593
|
+
var BaseAggregate = class extends Entity {
|
|
594
594
|
static {
|
|
595
|
-
__name(this, "
|
|
595
|
+
__name(this, "BaseAggregate");
|
|
596
596
|
}
|
|
597
597
|
_version = 0;
|
|
598
|
+
/**
|
|
599
|
+
* DB-baseline version. `undefined` until the aggregate has been
|
|
600
|
+
* persisted or restored at least once. Repository implementations
|
|
601
|
+
* route INSERT vs UPDATE on this field and use it as the OCC
|
|
602
|
+
* baseline. See `IRepository.save` JSDoc.
|
|
603
|
+
*
|
|
604
|
+
* Distinct from {@link version}, which is the in-memory
|
|
605
|
+
* post-mutation value. Mutations bump `_version` but never touch
|
|
606
|
+
* `_persistedVersion` — that field only moves on {@link markRestored}
|
|
607
|
+
* (Post-Load) and {@link markPersisted} (Post-Save).
|
|
608
|
+
*/
|
|
609
|
+
_persistedVersion = void 0;
|
|
610
|
+
_pendingEvents = [];
|
|
598
611
|
get version() {
|
|
599
612
|
return this._version;
|
|
600
613
|
}
|
|
601
|
-
|
|
602
|
-
this.
|
|
614
|
+
get persistedVersion() {
|
|
615
|
+
return this._persistedVersion;
|
|
603
616
|
}
|
|
604
|
-
_config;
|
|
605
|
-
_autoVersionBump;
|
|
606
|
-
_pendingEvents = [];
|
|
607
617
|
/**
|
|
608
|
-
* Read-only list of domain events recorded on this aggregate that
|
|
609
|
-
* not yet been flushed to the outbox / persistence layer.
|
|
618
|
+
* Read-only list of domain events recorded on this aggregate that
|
|
619
|
+
* have not yet been flushed to the outbox / persistence layer.
|
|
610
620
|
*/
|
|
611
621
|
get pendingEvents() {
|
|
612
622
|
return Object.freeze(this._pendingEvents.slice());
|
|
613
623
|
}
|
|
614
624
|
/**
|
|
615
|
-
* Clears the pending-event list.
|
|
616
|
-
*
|
|
625
|
+
* Clears the pending-event list. Called by `markPersisted` after a
|
|
626
|
+
* successful write — the events have been handed off to the outbox
|
|
627
|
+
* / event store and are no longer the aggregate's responsibility.
|
|
617
628
|
*/
|
|
618
629
|
clearPendingEvents() {
|
|
619
630
|
this._pendingEvents = [];
|
|
620
631
|
}
|
|
632
|
+
setVersion(version) {
|
|
633
|
+
this._version = version;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Manually bumps the aggregate version. Used by state-stored
|
|
637
|
+
* aggregates' `setState(_, true)` / `commit()` paths and by the
|
|
638
|
+
* event-sourced replay path after each applied event.
|
|
639
|
+
*/
|
|
640
|
+
bumpVersion() {
|
|
641
|
+
this.setVersion(this._version + 1);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* **Lifecycle marker — Post-Load.** Syncs both `_version` and
|
|
645
|
+
* `_persistedVersion` to the DB-stored version. Used by
|
|
646
|
+
* `reconstitute(...)` factories to assemble an in-memory aggregate
|
|
647
|
+
* from a persisted row.
|
|
648
|
+
*
|
|
649
|
+
* Does NOT fire {@link onPersisted} — that hook has post-save
|
|
650
|
+
* semantics (metrics, audit, cache eviction), not post-load. The
|
|
651
|
+
* Factory-vs-Reconstitution distinction (Vernon §11) is honoured
|
|
652
|
+
* structurally: two separate markers, one for each transition.
|
|
653
|
+
*
|
|
654
|
+
* @param version - The version the row currently holds in the DB
|
|
655
|
+
*
|
|
656
|
+
* @example
|
|
657
|
+
* ```ts
|
|
658
|
+
* static reconstitute(id: OrderId, state: OrderState, version: Version): Order {
|
|
659
|
+
* const order = new Order(id, state);
|
|
660
|
+
* order.markRestored(version);
|
|
661
|
+
* return order;
|
|
662
|
+
* }
|
|
663
|
+
* ```
|
|
664
|
+
*/
|
|
665
|
+
markRestored(version) {
|
|
666
|
+
this.setVersion(version);
|
|
667
|
+
this._persistedVersion = version;
|
|
668
|
+
}
|
|
621
669
|
/**
|
|
622
670
|
* **Framework lifecycle method — `@sealed`.** Called by `withCommit`
|
|
623
671
|
* (or by your own orchestration code, after harvesting `pendingEvents`)
|
|
@@ -639,7 +687,7 @@ var AggregateRoot = class extends Entity {
|
|
|
639
687
|
* @see onPersisted — the safe extension point for subclasses
|
|
640
688
|
*/
|
|
641
689
|
markPersisted(version) {
|
|
642
|
-
this.
|
|
690
|
+
this.markRestored(version);
|
|
643
691
|
this._pendingEvents = [];
|
|
644
692
|
this.onPersisted(version);
|
|
645
693
|
}
|
|
@@ -676,93 +724,28 @@ var AggregateRoot = class extends Entity {
|
|
|
676
724
|
onPersisted(_version) {
|
|
677
725
|
}
|
|
678
726
|
/**
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
* If it throws, the method propagates and **no event is recorded
|
|
687
|
-
* and no version is bumped**.
|
|
688
|
-
* 2. Each event in `events` is appended via `addDomainEvent`.
|
|
689
|
-
*
|
|
690
|
-
* `commit()` **always bumps the version**, regardless of the aggregate's
|
|
691
|
-
* `autoVersionBump` config. Recording a domain event implies "something
|
|
692
|
-
* happened that the outside world cares about", and optimistic-
|
|
693
|
-
* concurrency callers must see a fresh version every time. The config
|
|
694
|
-
* still governs the un-coupled `setState` path. If you need to mutate
|
|
695
|
-
* state without bumping (e.g. cosmetic caches), call `setState(newState,
|
|
696
|
-
* false)` and skip `commit` entirely.
|
|
697
|
-
*
|
|
698
|
-
* `events` accepts a single event or an array. Omit it (or pass `[]`)
|
|
699
|
-
* for state-only mutations.
|
|
700
|
-
*
|
|
701
|
-
* @example
|
|
702
|
-
* ```ts
|
|
703
|
-
* confirm(): void {
|
|
704
|
-
* if (this.state.status === "confirmed") {
|
|
705
|
-
* throw new OrderAlreadyConfirmedError(this.id);
|
|
706
|
-
* }
|
|
707
|
-
* this.commit(
|
|
708
|
-
* { ...this.state, status: "confirmed" },
|
|
709
|
-
* { type: "OrderConfirmed", orderId: this.id },
|
|
710
|
-
* );
|
|
711
|
-
* }
|
|
712
|
-
* ```
|
|
713
|
-
*
|
|
714
|
-
* `EventSourcedAggregate.apply()` enforces the same ordering
|
|
715
|
-
* structurally; `commit()` is the opt-in equivalent on `AggregateRoot`,
|
|
716
|
-
* where `setState` and `addDomainEvent` are otherwise decoupled and the
|
|
717
|
-
* ordering is convention-only.
|
|
718
|
-
*
|
|
719
|
-
* @param newState - The new state (validated by `validateState`)
|
|
720
|
-
* @param events - One event, an array of events, or none (default)
|
|
727
|
+
* Appends a domain event to the pending list. Prefer the higher-level
|
|
728
|
+
* `AggregateRoot.commit()` (state-stored) or `EventSourcedAggregate.apply()`
|
|
729
|
+
* (event-sourced) call sites — both wrap `addDomainEvent` in the
|
|
730
|
+
* canonical record-AFTER-mutation order (Vernon §8). Calling
|
|
731
|
+
* `addDomainEvent` directly is appropriate only when state and event
|
|
732
|
+
* recording have already been decoupled deliberately (e.g. a
|
|
733
|
+
* deletion event before a hard-delete; see `docs/guide/repository.md`).
|
|
721
734
|
*/
|
|
722
|
-
|
|
723
|
-
this.
|
|
724
|
-
const list = Array.isArray(events) ? events : [events];
|
|
725
|
-
for (const ev of list) {
|
|
726
|
-
this.addDomainEvent(ev);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
constructor(id, initialState, config) {
|
|
730
|
-
super(id, initialState);
|
|
731
|
-
this._config = config ?? {};
|
|
732
|
-
this._autoVersionBump = this._config.autoVersionBump ?? false;
|
|
735
|
+
addDomainEvent(event) {
|
|
736
|
+
this._pendingEvents.push(event);
|
|
733
737
|
}
|
|
734
738
|
/**
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
* explicit: a domain event describes something that has just happened
|
|
739
|
-
* to the aggregate — its existence implies the state change already
|
|
740
|
-
* occurred. Concretely:
|
|
741
|
-
*
|
|
742
|
-
* ```ts
|
|
743
|
-
* confirm(): void {
|
|
744
|
-
* if (this.state.status === "confirmed") {
|
|
745
|
-
* throw new OrderAlreadyConfirmedError(this.id);
|
|
746
|
-
* }
|
|
747
|
-
* this.setState({ ...this.state, status: "confirmed" }, true);
|
|
748
|
-
* this.addDomainEvent({ type: "OrderConfirmed", orderId: this.id });
|
|
749
|
-
* // ↑ post-mutation. The event represents the committed fact.
|
|
750
|
-
* }
|
|
751
|
-
* ```
|
|
752
|
-
*
|
|
753
|
-
* Recording before mutation is a footgun: if a subsequent invariant
|
|
754
|
-
* check throws, the event has already been queued but the state never
|
|
755
|
-
* actually changed — consumers see an event for a fact that did not
|
|
756
|
-
* happen.
|
|
757
|
-
*
|
|
758
|
-
* `EventSourcedAggregate.apply()` enforces this ordering structurally;
|
|
759
|
-
* `AggregateRoot` leaves it as a convention because the state-mutation
|
|
760
|
-
* path (`setState`) is decoupled from event recording.
|
|
761
|
-
*
|
|
762
|
-
* @param event - The domain event to record
|
|
739
|
+
* Creates a snapshot of the current aggregate state — the state at
|
|
740
|
+
* this moment plus the version. Useful for ES snapshot policies and
|
|
741
|
+
* for state-stored backup / restore.
|
|
763
742
|
*/
|
|
764
|
-
|
|
765
|
-
|
|
743
|
+
createSnapshot() {
|
|
744
|
+
return {
|
|
745
|
+
state: structuredClone(this._state),
|
|
746
|
+
version: this.version,
|
|
747
|
+
snapshotAt: /* @__PURE__ */ new Date()
|
|
748
|
+
};
|
|
766
749
|
}
|
|
767
750
|
/**
|
|
768
751
|
* Sugar for `createDomainEvent` that auto-injects `aggregateId`
|
|
@@ -805,21 +788,72 @@ var AggregateRoot = class extends Entity {
|
|
|
805
788
|
aggregateType: this.aggregateType
|
|
806
789
|
});
|
|
807
790
|
}
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// src/aggregate/aggregate-root.ts
|
|
794
|
+
var AggregateRoot = class extends BaseAggregate {
|
|
795
|
+
static {
|
|
796
|
+
__name(this, "AggregateRoot");
|
|
797
|
+
}
|
|
798
|
+
_autoVersionBump;
|
|
799
|
+
constructor(id, initialState, config) {
|
|
800
|
+
super(id, initialState);
|
|
801
|
+
this._autoVersionBump = config?.autoVersionBump ?? false;
|
|
802
|
+
}
|
|
808
803
|
/**
|
|
809
|
-
*
|
|
810
|
-
*
|
|
804
|
+
* Mutates state and records the resulting domain events in the
|
|
805
|
+
* **canonical record-after-mutation order**. Use this instead of calling
|
|
806
|
+
* `setState` + `addDomainEvent` separately and you cannot trip the
|
|
807
|
+
* "event for a fact that never happened" footgun.
|
|
808
|
+
*
|
|
809
|
+
* Order of operations:
|
|
810
|
+
* 1. `setState(newState, true)` — runs `validateState` first.
|
|
811
|
+
* If it throws, the method propagates and **no event is recorded
|
|
812
|
+
* and no version is bumped**.
|
|
813
|
+
* 2. Each event in `events` is appended via `addDomainEvent`.
|
|
811
814
|
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
815
|
+
* `commit()` **always bumps the version**, regardless of the aggregate's
|
|
816
|
+
* `autoVersionBump` config. Recording a domain event implies "something
|
|
817
|
+
* happened that the outside world cares about", and optimistic-
|
|
818
|
+
* concurrency callers must see a fresh version every time. The config
|
|
819
|
+
* still governs the un-coupled `setState` path. If you need to mutate
|
|
820
|
+
* state without bumping (e.g. cosmetic caches), call `setState(newState,
|
|
821
|
+
* false)` and skip `commit` entirely.
|
|
822
|
+
*
|
|
823
|
+
* `events` accepts a single event or an array. Omit it (or pass `[]`)
|
|
824
|
+
* for state-only mutations.
|
|
825
|
+
*
|
|
826
|
+
* @example
|
|
827
|
+
* ```ts
|
|
828
|
+
* confirm(): void {
|
|
829
|
+
* if (this.state.status === "confirmed") {
|
|
830
|
+
* throw new OrderAlreadyConfirmedError(this.id);
|
|
831
|
+
* }
|
|
832
|
+
* this.commit(
|
|
833
|
+
* { ...this.state, status: "confirmed" },
|
|
834
|
+
* this.recordEvent("OrderConfirmed", { orderId: this.id }),
|
|
835
|
+
* );
|
|
836
|
+
* }
|
|
837
|
+
* ```
|
|
838
|
+
*
|
|
839
|
+
* `EventSourcedAggregate.apply()` enforces the same ordering
|
|
840
|
+
* structurally; `commit()` is the opt-in equivalent on `AggregateRoot`,
|
|
841
|
+
* where `setState` and `addDomainEvent` are otherwise decoupled and the
|
|
842
|
+
* ordering is convention-only.
|
|
843
|
+
*
|
|
844
|
+
* @param newState - The new state (validated by `validateState`)
|
|
845
|
+
* @param events - One event, an array of events, or none (default)
|
|
814
846
|
*/
|
|
815
|
-
|
|
816
|
-
this.
|
|
847
|
+
commit(newState, events = []) {
|
|
848
|
+
this.setState(newState, true);
|
|
849
|
+
const list = Array.isArray(events) ? events : [events];
|
|
850
|
+
for (const ev of list) {
|
|
851
|
+
this.addDomainEvent(ev);
|
|
852
|
+
}
|
|
817
853
|
}
|
|
818
854
|
/**
|
|
819
855
|
* Sets the state and optionally bumps the version automatically.
|
|
820
|
-
*
|
|
821
|
-
* Automatically validates the newState using `validateState()`.
|
|
822
|
-
* Overrides Entity.setState to add version bumping.
|
|
856
|
+
* Validates `newState` via `validateState()`.
|
|
823
857
|
*
|
|
824
858
|
* @param newState - The new state
|
|
825
859
|
* @param bumpVersion - Whether to bump the version (defaults to autoVersionBump config)
|
|
@@ -832,43 +866,17 @@ var AggregateRoot = class extends Entity {
|
|
|
832
866
|
}
|
|
833
867
|
}
|
|
834
868
|
/**
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
* @returns A snapshot containing the current state and version
|
|
839
|
-
*
|
|
840
|
-
* @example
|
|
841
|
-
* ```typescript
|
|
842
|
-
* const snapshot = aggregate.createSnapshot();
|
|
843
|
-
* await snapshotRepository.save(aggregate.id, snapshot);
|
|
844
|
-
* ```
|
|
845
|
-
*/
|
|
846
|
-
createSnapshot() {
|
|
847
|
-
return {
|
|
848
|
-
state: structuredClone(this._state),
|
|
849
|
-
version: this.version,
|
|
850
|
-
snapshotAt: /* @__PURE__ */ new Date()
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* Restores the aggregate from a snapshot.
|
|
855
|
-
* This is useful for loading aggregates from snapshots instead of
|
|
856
|
-
* rebuilding them from scratch.
|
|
857
|
-
* Validates the restored state.
|
|
869
|
+
* Restores the aggregate from a snapshot — loads state and aligns
|
|
870
|
+
* `version` + `persistedVersion` to the snapshot version. Validates
|
|
871
|
+
* the restored state.
|
|
858
872
|
*
|
|
859
873
|
* @param snapshot - The snapshot to restore from
|
|
860
|
-
*
|
|
861
|
-
* @example
|
|
862
|
-
* ```typescript
|
|
863
|
-
* const snapshot = await snapshotRepository.getLatest(aggregateId);
|
|
864
|
-
* aggregate.restoreFromSnapshot(snapshot);
|
|
865
|
-
* ```
|
|
866
874
|
*/
|
|
867
875
|
restoreFromSnapshot(snapshot) {
|
|
868
876
|
const cloned = structuredClone(snapshot.state);
|
|
869
877
|
this.validateState(cloned);
|
|
870
878
|
this._state = freezeShallow(cloned);
|
|
871
|
-
this.
|
|
879
|
+
this.markRestored(snapshot.version);
|
|
872
880
|
}
|
|
873
881
|
};
|
|
874
882
|
var DomainError = class extends BaseError {
|
|
@@ -929,118 +937,10 @@ var ConcurrencyConflictError = class extends InfrastructureError {
|
|
|
929
937
|
};
|
|
930
938
|
|
|
931
939
|
// src/aggregate/event-sourced-aggregate.ts
|
|
932
|
-
var EventSourcedAggregate = class extends
|
|
940
|
+
var EventSourcedAggregate = class extends BaseAggregate {
|
|
933
941
|
static {
|
|
934
942
|
__name(this, "EventSourcedAggregate");
|
|
935
943
|
}
|
|
936
|
-
// --- Version management (own, not inherited from AggregateRoot) ---
|
|
937
|
-
_version = 0;
|
|
938
|
-
get version() {
|
|
939
|
-
return this._version;
|
|
940
|
-
}
|
|
941
|
-
setVersion(version) {
|
|
942
|
-
this._version = version;
|
|
943
|
-
}
|
|
944
|
-
// --- Event tracking ---
|
|
945
|
-
_pendingEvents = [];
|
|
946
|
-
get pendingEvents() {
|
|
947
|
-
return Object.freeze(this._pendingEvents.slice());
|
|
948
|
-
}
|
|
949
|
-
clearPendingEvents() {
|
|
950
|
-
this._pendingEvents = [];
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* **Framework lifecycle method — `@sealed`.** Called by `withCommit`
|
|
954
|
-
* (or by your own orchestration code, after harvesting `pendingEvents`)
|
|
955
|
-
* to push the persisted version back into the in-memory aggregate and
|
|
956
|
-
* clear `pendingEvents`. TypeScript has no `final` keyword, but
|
|
957
|
-
* subclasses **should not** override this method directly.
|
|
958
|
-
*
|
|
959
|
-
* Overriding without calling `super.markPersisted(version)` silently
|
|
960
|
-
* leaks `pendingEvents` — the next `withCommit` will re-dispatch them
|
|
961
|
-
* through the outbox, double-emitting events. This bug has been hit
|
|
962
|
-
* in production by consumers; the {@link onPersisted} hook below is
|
|
963
|
-
* the safer extension point.
|
|
964
|
-
*
|
|
965
|
-
* If you must override (legitimate cases are very rare), call
|
|
966
|
-
* `super.markPersisted(version)` FIRST so the framework's cleanup
|
|
967
|
-
* runs, then add your logic afterwards.
|
|
968
|
-
*
|
|
969
|
-
* @param version - The version assigned by the persistence layer
|
|
970
|
-
* @see onPersisted — the safe extension point for subclasses
|
|
971
|
-
*/
|
|
972
|
-
markPersisted(version) {
|
|
973
|
-
this.setVersion(version);
|
|
974
|
-
this._pendingEvents = [];
|
|
975
|
-
this.onPersisted(version);
|
|
976
|
-
}
|
|
977
|
-
/**
|
|
978
|
-
* Subclass extension point — fires AFTER {@link markPersisted} has
|
|
979
|
-
* updated the version and cleared `pendingEvents`. Override this for
|
|
980
|
-
* post-persist logging, metrics, or cache-eviction without risk of
|
|
981
|
-
* breaking the framework's pendingEvents cleanup.
|
|
982
|
-
*
|
|
983
|
-
* The default implementation is a no-op. Subclasses do NOT need to
|
|
984
|
-
* call `super.onPersisted(version)` — there is nothing in the parent
|
|
985
|
-
* implementation to preserve.
|
|
986
|
-
*
|
|
987
|
-
* **`onPersisted` deliberately receives only the version, not the
|
|
988
|
-
* drained events.** Event-driven post-persist logic (aggregate-level
|
|
989
|
-
* audit logging, per-event-type side effects) belongs in `EventBus`
|
|
990
|
-
* subscribers or the outbox dispatcher — that is the proper
|
|
991
|
-
* Aggregate-Boundary separation. Building event-aware logic into
|
|
992
|
-
* `onPersisted` couples aggregate lifecycle to event processing and
|
|
993
|
-
* recreates the boundary problems Vernon's aggregate discipline is
|
|
994
|
-
* meant to prevent.
|
|
995
|
-
*
|
|
996
|
-
* **The hook must return synchronously.** `markPersisted` is `void`-
|
|
997
|
-
* typed and calls `onPersisted` without `await`. TypeScript's
|
|
998
|
-
* permissive `void` will accept an `async`-override returning
|
|
999
|
-
* `Promise<void>`, but the returned promise is fire-and-forget —
|
|
1000
|
-
* any rejection becomes an unhandled rejection and `withCommit`
|
|
1001
|
-
* proceeds without waiting. For asynchronous work, subscribe to the
|
|
1002
|
-
* relevant domain event on the `EventBus` instead; that is the
|
|
1003
|
-
* properly awaited extension point.
|
|
1004
|
-
*
|
|
1005
|
-
* @param version - The version that was just persisted
|
|
1006
|
-
*/
|
|
1007
|
-
onPersisted(_version) {
|
|
1008
|
-
}
|
|
1009
|
-
constructor(id, initialState) {
|
|
1010
|
-
super(id, initialState);
|
|
1011
|
-
}
|
|
1012
|
-
/**
|
|
1013
|
-
* Sugar for `createDomainEvent` that auto-injects `aggregateId`
|
|
1014
|
-
* (from `this.id`) and `aggregateType` (from {@link aggregateType})
|
|
1015
|
-
* into the event's metadata fields. The canonical path for
|
|
1016
|
-
* constructing events to feed into `apply()` from inside aggregate
|
|
1017
|
-
* domain methods.
|
|
1018
|
-
*
|
|
1019
|
-
* @example
|
|
1020
|
-
* ```ts
|
|
1021
|
-
* class Order extends EventSourcedAggregate<OrderState, OrderEvent, OrderId> {
|
|
1022
|
-
* protected readonly aggregateType = "Order";
|
|
1023
|
-
*
|
|
1024
|
-
* confirm(): void {
|
|
1025
|
-
* this.apply(this.recordEvent("OrderConfirmed", { orderId: this.id }));
|
|
1026
|
-
* }
|
|
1027
|
-
* }
|
|
1028
|
-
* ```
|
|
1029
|
-
*
|
|
1030
|
-
* Calling `createDomainEvent(...)` directly inside an aggregate
|
|
1031
|
-
* method leaves `aggregateId` and `aggregateType` unset; the
|
|
1032
|
-
* `withCommit` harvest boundary catches it at runtime, but
|
|
1033
|
-
* `this.recordEvent(...)` makes the right thing impossible to
|
|
1034
|
-
* forget.
|
|
1035
|
-
*/
|
|
1036
|
-
recordEvent(type, payload, options) {
|
|
1037
|
-
return createDomainEvent(type, payload, {
|
|
1038
|
-
...options,
|
|
1039
|
-
aggregateId: this.id,
|
|
1040
|
-
aggregateType: this.aggregateType
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
// --- Event application ---
|
|
1044
944
|
/**
|
|
1045
945
|
* Validates an event before it is applied. Default is no-op.
|
|
1046
946
|
* Subclasses override to throw a concrete `DomainError` subclass when
|
|
@@ -1085,11 +985,10 @@ var EventSourcedAggregate = class extends Entity {
|
|
|
1085
985
|
const nextState = handler(this._state, event);
|
|
1086
986
|
this._state = freezeShallow(nextState);
|
|
1087
987
|
if (isNew) {
|
|
1088
|
-
this.
|
|
1089
|
-
this.
|
|
988
|
+
this.addDomainEvent(event);
|
|
989
|
+
this.bumpVersion();
|
|
1090
990
|
}
|
|
1091
991
|
}
|
|
1092
|
-
// --- History & Snapshots ---
|
|
1093
992
|
/**
|
|
1094
993
|
* Reconstitutes the aggregate from an event history. Catches `DomainError`
|
|
1095
994
|
* thrown during replay and returns it as an `Err` — this is the
|
|
@@ -1102,7 +1001,7 @@ var EventSourcedAggregate = class extends Entity {
|
|
|
1102
1001
|
* 2 events ends at v=3, not v=2.
|
|
1103
1002
|
*/
|
|
1104
1003
|
loadFromHistory(history) {
|
|
1105
|
-
const startVersion = this.
|
|
1004
|
+
const startVersion = this.version;
|
|
1106
1005
|
for (const event of history) {
|
|
1107
1006
|
try {
|
|
1108
1007
|
this.dispatchAndCommit(event, false);
|
|
@@ -1111,19 +1010,9 @@ var EventSourcedAggregate = class extends Entity {
|
|
|
1111
1010
|
throw e;
|
|
1112
1011
|
}
|
|
1113
1012
|
}
|
|
1114
|
-
this.
|
|
1013
|
+
this.markRestored(startVersion + history.length);
|
|
1115
1014
|
return ok();
|
|
1116
1015
|
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Creates a snapshot of the current aggregate state.
|
|
1119
|
-
*/
|
|
1120
|
-
createSnapshot() {
|
|
1121
|
-
return {
|
|
1122
|
-
state: structuredClone(this._state),
|
|
1123
|
-
version: this._version,
|
|
1124
|
-
snapshotAt: /* @__PURE__ */ new Date()
|
|
1125
|
-
};
|
|
1126
|
-
}
|
|
1127
1016
|
/**
|
|
1128
1017
|
* Restores the aggregate from a snapshot and applies events that occurred
|
|
1129
1018
|
* after. Same infrastructure-boundary semantics as `loadFromHistory`:
|
|
@@ -1136,7 +1025,7 @@ var EventSourcedAggregate = class extends Entity {
|
|
|
1136
1025
|
*/
|
|
1137
1026
|
restoreFromSnapshotWithEvents(snapshot, eventsAfterSnapshot) {
|
|
1138
1027
|
const previousState = this._state;
|
|
1139
|
-
const previousVersion = this.
|
|
1028
|
+
const previousVersion = this.version;
|
|
1140
1029
|
this._state = freezeShallow(structuredClone(snapshot.state));
|
|
1141
1030
|
this.setVersion(snapshot.version);
|
|
1142
1031
|
for (const event of eventsAfterSnapshot) {
|
|
@@ -1149,7 +1038,9 @@ var EventSourcedAggregate = class extends Entity {
|
|
|
1149
1038
|
throw e;
|
|
1150
1039
|
}
|
|
1151
1040
|
}
|
|
1152
|
-
this.
|
|
1041
|
+
this.markRestored(
|
|
1042
|
+
snapshot.version + eventsAfterSnapshot.length
|
|
1043
|
+
);
|
|
1153
1044
|
return ok();
|
|
1154
1045
|
}
|
|
1155
1046
|
};
|
|
@@ -1369,7 +1260,13 @@ var InMemoryOutbox = class {
|
|
|
1369
1260
|
for (const id of dispatchIds) this.pending.delete(id);
|
|
1370
1261
|
}
|
|
1371
1262
|
};
|
|
1263
|
+
function voValidated(t, validate, message = "Validation failed") {
|
|
1264
|
+
const issues = new ValidationError(message);
|
|
1265
|
+
validate(issues, t);
|
|
1266
|
+
return issues.hasIssues() ? err(issues) : ok(vo(t));
|
|
1267
|
+
}
|
|
1268
|
+
__name(voValidated, "voValidated");
|
|
1372
1269
|
|
|
1373
|
-
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, withClockFactory, withCommit, withEventIdFactory };
|
|
1270
|
+
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, voValidated, voWithValidation, withClockFactory, withCommit, withEventIdFactory };
|
|
1374
1271
|
//# sourceMappingURL=index.js.map
|
|
1375
1272
|
//# sourceMappingURL=index.js.map
|