@shirudo/ddd-kit 3.0.0-rc.10 → 3.0.0-rc.11

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.
@@ -279,16 +279,16 @@ var FoldReturnedNoStateError = class extends KitWiringError {
279
279
  * and a handler.
280
280
  */
281
281
  var DirectStateMutationError = class extends KitWiringError {
282
- aggregateId;
283
- constructor(aggregateId) {
284
- super("DIRECT_STATE_MUTATION", `Aggregate ${aggregateId} is event-sourced: its state changes only through apply(). Record the fact as an event and fold it in a handler instead of calling setState.`);
285
- this.aggregateId = aggregateId;
282
+ identity;
283
+ constructor(options) {
284
+ super("DIRECT_STATE_MUTATION", `Aggregate ${describeAggregateIdentity(options.identity)} is event-sourced: its state changes only through apply(). Record the fact as an event and fold it in a handler instead of calling setState.`);
285
+ this.identity = detachAggregateIdentity(options.identity);
286
286
  }
287
287
  };
288
288
  /**
289
289
  * Thrown by `Projector.project` when an event cannot be projected
290
290
  * safely because its cursor is missing or malformed, or its aggregate
291
- * address is absent. Applying such an event would break idempotency, so
291
+ * identity is absent. Applying such an event would break idempotency, so
292
292
  * the batch fails. Events written by `withCommit` carry the complete
293
293
  * cursor automatically; other sources compose a gap-proof committed-event
294
294
  * envelope. A well-formed cursor that does not continue the stored chain
@@ -517,39 +517,68 @@ var InvalidVersionError = class extends KitWiringError {
517
517
  * discard a dirty instance and replay into a fresh one.
518
518
  */
519
519
  var UnreplayableAggregateError = class extends KitWiringError {
520
- aggregateId;
521
- constructor(aggregateId, reason) {
522
- super("UNREPLAYABLE_AGGREGATE", `Cannot replay onto aggregate ${aggregateId}: ${reason}. Reconstitute on a fresh instance (no factory-recorded events, no unpersisted mutations).`);
523
- this.aggregateId = aggregateId;
520
+ identity;
521
+ constructor(options) {
522
+ super("UNREPLAYABLE_AGGREGATE", `Cannot replay onto aggregate ${describeAggregateIdentity(options.identity)}: ${options.reason}. Reconstitute on a fresh instance (no factory-recorded events, no unpersisted mutations).`);
523
+ this.identity = detachAggregateIdentity(options.identity);
524
524
  }
525
525
  };
526
- /** The address the event names; a missing field falls back to the receiving aggregate. */
527
- function describeEventAddress(options) {
526
+ /**
527
+ * Renders an aggregate identity for a kit message as `Type(id)`, the one
528
+ * format every kit message uses, so a log search finds all of them. Kit
529
+ * modules only; not part of the package entries.
530
+ */
531
+ function describeAggregateIdentity(identity) {
532
+ return `${identity.aggregateType}(${identity.aggregateId})`;
533
+ }
534
+ /**
535
+ * A frozen copy of the two aggregate identity fields. An error keeps its own
536
+ * copy, so it neither shares the caller's object nor carries its other
537
+ * properties into the log. Kit modules only; not part of the package
538
+ * entries.
539
+ */
540
+ function detachAggregateIdentity(identity) {
541
+ return Object.freeze({
542
+ aggregateType: identity.aggregateType,
543
+ aggregateId: identity.aggregateId
544
+ });
545
+ }
546
+ function detachPartialAggregateIdentity(identity) {
547
+ return Object.freeze({
548
+ ...identity.aggregateType === void 0 ? {} : { aggregateType: identity.aggregateType },
549
+ ...identity.aggregateId === void 0 ? {} : { aggregateId: identity.aggregateId }
550
+ });
551
+ }
552
+ /** The identity the event names; a missing field falls back to the receiving aggregate. */
553
+ function describeEventIdentity(options) {
528
554
  const { expected, actual } = options;
529
- return `${actual.aggregateType ?? expected.aggregateType} ${actual.aggregateId ?? expected.aggregateId}`;
555
+ return describeAggregateIdentity({
556
+ aggregateType: actual.aggregateType ?? expected.aggregateType,
557
+ aggregateId: actual.aggregateId ?? expected.aggregateId
558
+ });
530
559
  }
531
560
  /**
532
561
  * Thrown by `EventSourcedAggregate.apply()` when a NEW event carries an
533
562
  * `aggregateId` or `aggregateType` naming a different aggregate: a
534
563
  * deterministic programming bug at the call site (a hand-built or
535
- * copied event addressed elsewhere), caught before the event can be
536
- * recorded and poison the own stream. Events with MISSING address
537
- * fields do not trip this: `apply()` stamps them from the aggregate,
564
+ * copied event that belongs elsewhere), caught before the event can be
565
+ * recorded and poison the own stream. Events with MISSING aggregate
566
+ * identity fields do not trip this: `apply()` stamps them from the aggregate,
538
567
  * the same guarantee `createEvent` gives. A wiring error, distinct
539
568
  * from {@link ForeignEventError} on purpose: a wrong new event is a
540
569
  * bug in today's code, a wrong PERSISTED row is corrupted or miswired
541
570
  * infrastructure, and handlers for one must not absorb the other.
542
571
  */
543
- var MisaddressedEventError = class extends KitWiringError {
544
- /** Address of the aggregate that received the event. */
572
+ var MisattributedEventError = class extends KitWiringError {
573
+ /** Identity of the aggregate that received the event. */
545
574
  expected;
546
- /** Address fields the event carries. */
575
+ /** Aggregate identity fields the event carries. */
547
576
  actual;
548
577
  eventType;
549
578
  constructor(options) {
550
- super("MISADDRESSED_EVENT", `New event "${options.eventType}" is addressed to ${describeEventAddress(options)} but was applied on ${options.expected.aggregateType} ${options.expected.aggregateId}: fix the call site (createEvent stamps the right address).`);
551
- this.expected = options.expected;
552
- this.actual = options.actual;
579
+ super("MISATTRIBUTED_EVENT", `New event "${options.eventType}" belongs to ${describeEventIdentity(options)} but was applied on ${describeAggregateIdentity(options.expected)}: fix the call site (createEvent stamps the right identity).`);
580
+ this.expected = detachAggregateIdentity(options.expected);
581
+ this.actual = detachPartialAggregateIdentity(options.actual);
553
582
  this.eventType = options.eventType;
554
583
  }
555
584
  };
@@ -562,14 +591,12 @@ var MisaddressedEventError = class extends KitWiringError {
562
591
  * channel would mask it as perpetual silent refolding.
563
592
  */
564
593
  var SnapshotVersionNotRestoredError = class extends KitWiringError {
565
- aggregateType;
566
- aggregateId;
594
+ identity;
567
595
  snapshotVersion;
568
596
  restoredVersion;
569
597
  constructor(options) {
570
- super("SNAPSHOT_VERSION_NOT_RESTORED", `SnapshotModel.reconstitute for ${options.aggregateType} ${options.aggregateId} returned an aggregate at version ${options.restoredVersion} for a snapshot at version ${options.snapshotVersion}. Reconstitution must restore the persisted version; call markReconstituted(version) inside the aggregate factory.`);
571
- this.aggregateType = options.aggregateType;
572
- this.aggregateId = options.aggregateId;
598
+ super("SNAPSHOT_VERSION_NOT_RESTORED", `SnapshotModel.reconstitute for ${describeAggregateIdentity(options.identity)} returned an aggregate at version ${options.restoredVersion} for a snapshot at version ${options.snapshotVersion}. Reconstitution must restore the persisted version; call markReconstituted(version) inside the aggregate factory.`);
599
+ this.identity = detachAggregateIdentity(options.identity);
573
600
  this.snapshotVersion = options.snapshotVersion;
574
601
  this.restoredVersion = options.restoredVersion;
575
602
  }
@@ -626,8 +653,10 @@ var UnmintedEventError = class extends KitWiringError {
626
653
  * stamp providers free of domain decisions.
627
654
  */
628
655
  var ReentrantEventRecordingError = class extends KitWiringError {
629
- constructor(aggregateId) {
630
- super("REENTRANT_EVENT_RECORDING", `Pending events of aggregate ${aggregateId} changed while recordPendingEvents was stamping them. A stamp provider must not trigger new decisions on the aggregate being recorded; make every domain decision first, then record.`);
656
+ identity;
657
+ constructor(options) {
658
+ super("REENTRANT_EVENT_RECORDING", `Pending events of aggregate ${describeAggregateIdentity(options.identity)} changed while recordPendingEvents was stamping them. A stamp provider must not trigger new decisions on the aggregate being recorded; make every domain decision first, then record.`);
659
+ this.identity = detachAggregateIdentity(options.identity);
631
660
  }
632
661
  };
633
662
  /**
@@ -642,10 +671,13 @@ var ReentrantEventRecordingError = class extends KitWiringError {
642
671
  * identity per fact.
643
672
  */
644
673
  var DuplicateEventIdError = class extends KitWiringError {
674
+ identity;
675
+ /** The event id two pending events would have shared. */
645
676
  eventId;
646
- constructor(aggregateId, eventId) {
647
- super("DUPLICATE_EVENT_ID", `Two pending events of aggregate ${aggregateId} carry the same eventId "${eventId}". Each fact needs its own identity: append a recorded event once, and return a fresh stamp per decision from the stamp provider.`);
648
- this.eventId = eventId;
677
+ constructor(options) {
678
+ super("DUPLICATE_EVENT_ID", `Two pending events of aggregate ${describeAggregateIdentity(options.identity)} carry the same eventId "${options.eventId}". Each fact needs its own event id: append a recorded event once, and return a fresh stamp per decision from the stamp provider.`);
679
+ this.identity = detachAggregateIdentity(options.identity);
680
+ this.eventId = options.eventId;
649
681
  }
650
682
  };
651
683
  /**
@@ -658,15 +690,13 @@ var DuplicateEventIdError = class extends KitWiringError {
658
690
  * per decision.
659
691
  */
660
692
  var PendingEventLimitExceededError = class extends KitWiringError {
661
- aggregateType;
662
- aggregateId;
693
+ identity;
663
694
  limit;
664
695
  pending;
665
696
  added;
666
697
  constructor(options) {
667
- super("PENDING_EVENT_LIMIT_EXCEEDED", `Aggregate ${options.aggregateType}(${options.aggregateId}) holds ${options.pending} pending event(s) and cannot record ${options.added} more: maxPendingEvents is ${options.limit}. A decision that emits this many facts points at a missing aggregate boundary.`);
668
- this.aggregateType = options.aggregateType;
669
- this.aggregateId = options.aggregateId;
698
+ super("PENDING_EVENT_LIMIT_EXCEEDED", `Aggregate ${describeAggregateIdentity(options.identity)} holds ${options.pending} pending event(s) and cannot record ${options.added} more: maxPendingEvents is ${options.limit}. A decision that emits this many facts points at a missing aggregate boundary.`);
699
+ this.identity = detachAggregateIdentity(options.identity);
670
700
  this.limit = options.limit;
671
701
  this.pending = options.pending;
672
702
  this.added = options.added;
@@ -682,14 +712,14 @@ var PendingEventLimitExceededError = class extends KitWiringError {
682
712
  * orchestration: acknowledge exactly the batch that was enrolled, once.
683
713
  */
684
714
  var PendingEventBatchMismatchError = class extends KitWiringError {
685
- aggregateId;
715
+ identity;
686
716
  batchLength;
687
717
  pendingLength;
688
- constructor(aggregateId, batchLength, pendingLength) {
689
- super("PENDING_EVENT_BATCH_MISMATCH", `The committed batch of ${batchLength} event(s) is no longer the pending prefix of aggregate ${aggregateId} (${pendingLength} pending). Acknowledge exactly the batch that was enrolled, once.`);
690
- this.aggregateId = aggregateId;
691
- this.batchLength = batchLength;
692
- this.pendingLength = pendingLength;
718
+ constructor(options) {
719
+ super("PENDING_EVENT_BATCH_MISMATCH", `The committed batch of ${options.batchLength} event(s) is no longer the pending prefix of aggregate ${describeAggregateIdentity(options.identity)} (${options.pendingLength} pending). Acknowledge exactly the batch that was enrolled, once.`);
720
+ this.identity = detachAggregateIdentity(options.identity);
721
+ this.batchLength = options.batchLength;
722
+ this.pendingLength = options.pendingLength;
693
723
  }
694
724
  };
695
725
  /**
@@ -699,89 +729,176 @@ var PendingEventBatchMismatchError = class extends KitWiringError {
699
729
  * the persisted row belongs to someone else (a miswired stream read,
700
730
  * ids colliding across aggregate types, a corrupted store). An
701
731
  * `InfrastructureError`, NOT a `DomainError` (same posture as
702
- * {@link SnapshotSchemaMismatchError}): a wrong address is data
732
+ * {@link SnapshotSchemaMismatchError}): a wrong aggregate identity is data
703
733
  * corruption or wiring, never an expected business rejection, so it
704
734
  * must not be absorbed by generic domain error handling or presented
705
735
  * as a 4xx. It therefore PROPAGATES as a throw through the replay
706
736
  * methods' `Result` contract (which reserves `Err` for `DomainError`),
707
737
  * after the usual all-or-nothing rollback. History events without the
708
- * optional address fields pass unchecked (the fields are optional on
738
+ * optional aggregate identity fields pass unchecked (the fields are optional on
709
739
  * the event shape); new events are covered by
710
- * {@link MisaddressedEventError}.
740
+ * {@link MisattributedEventError}.
711
741
  */
712
742
  var ForeignEventError = class extends InfrastructureError {
713
- /** Address of the aggregate that received the event. */
743
+ /** Identity of the aggregate that received the event. */
714
744
  expected;
715
- /** Address fields the event carries. */
745
+ /** Aggregate identity fields the event carries. */
716
746
  actual;
717
747
  eventType;
718
748
  constructor(options) {
719
749
  super({
720
750
  code: "FOREIGN_EVENT",
721
- message: `Persisted event "${options.eventType}" belongs to ${describeEventAddress(options)}, not to ${options.expected.aggregateType} ${options.expected.aggregateId}: the stream row addresses a different aggregate.`
751
+ message: `Persisted event "${options.eventType}" belongs to ${describeEventIdentity(options)}, not to ${describeAggregateIdentity(options.expected)}: the stream row belongs to a different aggregate.`
722
752
  });
723
- this.expected = options.expected;
724
- this.actual = options.actual;
753
+ this.expected = detachAggregateIdentity(options.expected);
754
+ this.actual = detachPartialAggregateIdentity(options.actual);
725
755
  this.eventType = options.eventType;
726
756
  }
727
757
  };
758
+ function describeReportedHead(head) {
759
+ if (typeof head === "number" && Number.isFinite(head)) return String(head);
760
+ return `${showReportedHead(head)} of type ${typeof head}`;
761
+ }
762
+ function showReportedHead(head) {
763
+ if (typeof head === "string") return JSON.stringify(head);
764
+ if (typeof head === "object" && head !== null) return Object.prototype.toString.call(head);
765
+ return String(head);
766
+ }
767
+ function eventStreamPageReasonMessage(stream, options) {
768
+ const target = `target version ${String(options.targetVersion)}`;
769
+ switch (options.reason) {
770
+ case "empty_page": return `The read of ${stream} returned an empty page after version ${options.fromVersion} toward ${target}. A page holds at least one event while events remain.`;
771
+ case "stream_vanished": return `readStream reported ${stream} absent after version ${options.fromVersion} while the replay had not reached ${target}. A stream is append-only, and only a physical removal deletes it; check for a removal that ran during the read.`;
772
+ case "page_past_target": return `The read of ${stream} returned ${String(options.eventCount)} events after version ${options.fromVersion}, more than the window (${options.fromVersion}, ${String(options.targetVersion)}] holds.`;
773
+ case "page_over_limit": return `The read of ${stream} returned ${String(options.eventCount)} events after version ${options.fromVersion}, more than the limit of ${String(options.limit)} it asked for.`;
774
+ case "head_regressed": return `readStream reported ${stream} at head ${String(options.lastVersion)} after version ${options.fromVersion}, below the head ${String(options.firstPageLastVersion)} of the first page. A stream is append-only, so its head never moves back.`;
775
+ case "invalid_head": return `readStream reported ${stream} with head ${describeReportedHead(options.lastVersion)} after version ${options.fromVersion}. The head of an existing stream is a safe integer of at least 1; report a stream without events as absent.`;
776
+ }
777
+ }
728
778
  /**
729
- * Thrown by a paged EventStore consumer when `readStream` returns no events
730
- * even though its continuation cursor has not reached the pinned target.
731
- * Such a page cannot advance and violates the EventStore port contract; a
732
- * replay loop that merely continued would spin forever.
779
+ * Thrown when a page of a stream read breaks the `readStream` contract.
780
+ * `readStreamPages` checks every page it reads, and
781
+ * `reconstituteAggregateFromStreamPages` checks every page an adapter that
782
+ * pages on its own hands over. Both throw this error for the same defect.
783
+ *
784
+ * The `reason` names the defect. `empty_page`: the page holds no event,
785
+ * but events remain before the target version. `stream_vanished`: a
786
+ * continuation page reports the stream absent. `page_past_target`: the
787
+ * page holds more events than its window has left. `page_over_limit`: the
788
+ * page holds more events than the `limit` of the read. `head_regressed`: a
789
+ * continuation page reports a head below the head of the first page.
790
+ * `invalid_head`: a page of an existing stream reports a head that is not
791
+ * a safe integer of at least 1, for example `0` for a stream without
792
+ * events, or a string from a driver that returns big integers as text.
733
793
  *
734
- * This is a non-retryable infrastructure error: the persistence adapter
735
- * deterministically contradicted its port contract, so retrying the same read
736
- * is not a recovery policy. Run `createEventStoreContractTests` against the
737
- * adapter and fix its windowing/continuation implementation.
794
+ * No case is retryable. Fix the adapter: run `createEventStoreContractTests`
795
+ * against an EventStore adapter, and `createReplayableStreamPagesContractTests`
796
+ * against an adapter that pages on its own. Two causes lie outside the
797
+ * adapter. A stream is append-only, and only a physical removal deletes it
798
+ * (the repository guide, "Domain deletion versus physical removal"), so a
799
+ * removal that runs during the read gives `stream_vanished`. An upcaster
800
+ * that splits one stored event into several gives `page_past_target` at
801
+ * the replay, because the replay counts one version per event.
738
802
  */
739
- var NonProgressingEventStreamPageError = class extends InfrastructureError {
740
- aggregateType;
741
- aggregateId;
803
+ var InvalidEventStreamPageError = class extends InfrastructureError {
804
+ identity;
805
+ reason;
742
806
  fromVersion;
743
807
  targetVersion;
808
+ lastVersion;
809
+ firstPageLastVersion;
810
+ eventCount;
811
+ limit;
744
812
  constructor(options) {
813
+ const stream = `${describeAggregateIdentity(options.identity)}`;
745
814
  super({
746
- code: "NON_PROGRESSING_EVENT_STREAM_PAGE",
747
- message: `EventStore returned no events for ${options.aggregateType}(${options.aggregateId}) after version ${options.fromVersion}, before pinned target version ${options.targetVersion}. The page cannot advance; run the EventStore contract suite and fix the adapter's continuation window.`
815
+ code: "INVALID_EVENT_STREAM_PAGE",
816
+ message: eventStreamPageReasonMessage(stream, options)
748
817
  });
749
- this.aggregateType = options.aggregateType;
750
- this.aggregateId = options.aggregateId;
818
+ this.identity = detachAggregateIdentity(options.identity);
819
+ this.reason = options.reason;
751
820
  this.fromVersion = options.fromVersion;
752
821
  this.targetVersion = options.targetVersion;
822
+ this.lastVersion = options.lastVersion;
823
+ this.firstPageLastVersion = options.firstPageLastVersion;
824
+ this.eventCount = options.eventCount;
825
+ this.limit = options.limit;
753
826
  }
754
827
  };
828
+ function replayTargetMismatchMessage(stream, options) {
829
+ switch (options.reason) {
830
+ case "target_not_at_cursor": return `Replay target for ${stream} stands at version ${options.actualVersion}, but the read continues after version ${options.fromVersion}. The reconstitution factory reports a version other than the read cursor.`;
831
+ case "pages_short_of_target": return `Replay of ${stream} ended at version ${options.actualVersion}, short of the pinned target version ${options.targetVersion}. The read stopped before the target version, the stream has a gap after version ${options.actualVersion}, or an upcaster merged stored events.`;
832
+ }
833
+ }
755
834
  /**
756
- * Thrown by a load recipe when the replayed aggregate does not end at the
757
- * pinned stream head. Events carry no stream position, so the aggregate
758
- * cannot detect a tail that overlaps the restored version or a page that
759
- * lies outside the requested window; only the caller, which pinned the
760
- * head, can compare. A snapshot catch-up passes only the events after the
761
- * restored version, and the final version must equal the head.
835
+ * Thrown by `reconstituteAggregateFromStreamPages`, or by a load recipe
836
+ * that replays on its own, when the replay target does not line up with the
837
+ * read. Events carry no stream position, so the aggregate cannot detect a
838
+ * tail that overlaps or misses the version it was reconstituted at. Only
839
+ * the caller, which pinned the target version, can compare. A single page
840
+ * that breaks its window is an {@link InvalidEventStreamPageError} instead.
762
841
  *
763
- * This is a non-retryable infrastructure error: the persistence adapter
764
- * contradicted its port contract. Run `createEventStoreContractTests` and
765
- * `createEsRepositoryContractTests` against the adapter and fix its
766
- * windowing.
842
+ * The `reason` names the check that failed. `target_not_at_cursor`: the
843
+ * replay target stands at a version other than the `fromVersion` the read
844
+ * used, found before any page is replayed; a reconstitution factory reports
845
+ * the wrong version. `pages_short_of_target`: the pages ended before the
846
+ * target version. The read stopped early, the stream has a gap, or an
847
+ * upcaster merged several stored events into one. For an EventStore
848
+ * adapter, run `createEventStoreContractTests` and
849
+ * `createEsRepositoryContractTests` against it and fix its windowing. None
850
+ * of the cases is retryable. A snapshot beyond its stream does not reach
851
+ * the replay: `readStreamPages` reports that window as unreachable first,
852
+ * and an adapter that pages on its own must do the same.
767
853
  */
768
- var ReplayHeadMismatchError = class extends InfrastructureError {
769
- aggregateType;
770
- aggregateId;
854
+ var ReplayTargetMismatchError = class extends InfrastructureError {
855
+ identity;
856
+ reason;
857
+ fromVersion;
771
858
  targetVersion;
772
859
  actualVersion;
773
860
  constructor(options) {
861
+ const message = replayTargetMismatchMessage(`${describeAggregateIdentity(options.identity)}`, options);
774
862
  super({
775
- code: "REPLAY_HEAD_MISMATCH",
776
- message: `Replay of ${options.aggregateType}(${options.aggregateId}) ended at version ${options.actualVersion}, not at the pinned stream head ${options.targetVersion}. The tail overlapped the restored version or a page lay outside the requested window; pass only the events after the restored version.`
863
+ code: "REPLAY_TARGET_MISMATCH",
864
+ message
777
865
  });
778
- this.aggregateType = options.aggregateType;
779
- this.aggregateId = options.aggregateId;
866
+ this.identity = detachAggregateIdentity(options.identity);
867
+ this.reason = options.reason;
868
+ this.fromVersion = options.fromVersion;
780
869
  this.targetVersion = options.targetVersion;
781
870
  this.actualVersion = options.actualVersion;
782
871
  }
783
872
  };
784
873
  /**
874
+ * The `Err` of `reconstituteAggregateFromStreamPages`: the aggregate
875
+ * rejected a stored event while it replayed a page of the stream.
876
+ *
877
+ * The window `(fromVersion, toVersion]` locates the rejected page, and
878
+ * `cause` holds the `DomainError` of the aggregate. A stored stream that
879
+ * the domain cannot replay is a defect of the stored data, not a request
880
+ * the caller can correct. So this is an `InfrastructureError`, and it is
881
+ * not retryable. An empty window means that the replay target rejected an
882
+ * empty history before the first page.
883
+ */
884
+ var ReplayRejectedError = class extends InfrastructureError {
885
+ identity;
886
+ fromVersion;
887
+ toVersion;
888
+ constructor(options) {
889
+ const stream = `${describeAggregateIdentity(options.identity)}`;
890
+ const rejected = `${options.cause.code}: ${options.cause.message}`;
891
+ super({
892
+ code: "REPLAY_REJECTED",
893
+ message: options.fromVersion === options.toVersion ? `The replay target of ${stream} rejected an empty history at version ${options.fromVersion} with ${rejected}` : `The aggregate ${stream} rejected a stored event in the page (${options.fromVersion}, ${options.toVersion}] with ${rejected}`,
894
+ cause: options.cause
895
+ });
896
+ this.identity = detachAggregateIdentity(options.identity);
897
+ this.fromVersion = options.fromVersion;
898
+ this.toVersion = options.toVersion;
899
+ }
900
+ };
901
+ /**
785
902
  * Thrown when an event harvested from an aggregate cannot be safely composed
786
903
  * into a commit envelope, or when an outbox can prove that accepting a
787
904
  * candidate would violate its event identity/source chain. Harvest failures
@@ -939,10 +1056,10 @@ var ErrorMapperFailedError = class extends KitWiringError {
939
1056
  * change.
940
1057
  */
941
1058
  var UnenrolledChangesError = class extends KitWiringError {
942
- aggregateId;
943
- constructor(aggregateId, options = {}) {
944
- super("UNENROLLED_CHANGES", `Aggregate ${aggregateId} was loaded and changed in this unit of work, ` + (options.appendOnly ? "but its repository is append-only and installs no update. An append-only aggregate must not change after add." : "but no update intent was registered. Call repository.update(aggregate) after the final domain decision so state and events flush together."));
945
- this.aggregateId = aggregateId;
1059
+ identity;
1060
+ constructor(options) {
1061
+ super("UNENROLLED_CHANGES", `Aggregate ${describeAggregateIdentity(options.identity)} was loaded and changed in this unit of work, ` + (options.appendOnly ? "but its repository is append-only and installs no update. An append-only aggregate must not change after add." : "but no update intent was registered. Call repository.update(aggregate) after the final domain decision so state and events flush together."));
1062
+ this.identity = detachAggregateIdentity(options.identity);
946
1063
  }
947
1064
  };
948
1065
  /**
@@ -956,50 +1073,44 @@ var UnenrolledChangesError = class extends KitWiringError {
956
1073
  * loud, not be absorbed by a generic infrastructure-error handler.
957
1074
  */
958
1075
  var AggregateDeletedError = class extends KitWiringError {
959
- aggregateId;
960
- constructor(aggregateId) {
961
- super("AGGREGATE_DELETED", `Aggregate ${aggregateId} was removed in this unit of work and cannot be added, updated, tracked, or removed through another instance again. Removal is final within an operation. A repeated remove of the SAME instance is an accepted no-op; if the aggregate must remain, do not remove it.`);
962
- this.aggregateId = aggregateId;
1076
+ identity;
1077
+ constructor(options) {
1078
+ super("AGGREGATE_DELETED", `Aggregate ${describeAggregateIdentity(options.identity)} was removed in this unit of work and cannot be added, updated, tracked, or removed through another instance again. Removal is final within an operation. A repeated remove of the SAME instance is an accepted no-op; if the aggregate must remain, do not remove it.`);
1079
+ this.identity = detachAggregateIdentity(options.identity);
963
1080
  }
964
1081
  };
965
1082
  var AggregateNotFoundError = class extends InfrastructureError {
966
- aggregateType;
967
- id;
1083
+ identity;
968
1084
  constructor(options) {
969
1085
  super({
970
1086
  code: "AGGREGATE_NOT_FOUND",
971
- message: `Aggregate not found: ${options.aggregateType}(${options.id})`,
1087
+ message: `Aggregate not found: ${describeAggregateIdentity(options.identity)}`,
972
1088
  cause: options.cause
973
1089
  });
974
- this.aggregateType = options.aggregateType;
975
- this.id = options.id;
1090
+ this.identity = detachAggregateIdentity(options.identity);
976
1091
  }
977
1092
  };
978
1093
  var DuplicateAggregateError = class extends InfrastructureError {
979
- aggregateType;
980
- aggregateId;
1094
+ identity;
981
1095
  constructor(options) {
982
1096
  super({
983
1097
  code: "DUPLICATE_AGGREGATE",
984
- message: `Duplicate aggregate: ${options.aggregateType}(${options.aggregateId}) already exists`,
1098
+ message: `Duplicate aggregate: ${describeAggregateIdentity(options.identity)} already exists`,
985
1099
  cause: options.cause
986
1100
  });
987
- this.aggregateType = options.aggregateType;
988
- this.aggregateId = options.aggregateId;
1101
+ this.identity = detachAggregateIdentity(options.identity);
989
1102
  }
990
1103
  };
991
1104
  var SnapshotSchemaMismatchError = class extends InfrastructureError {
992
- aggregateType;
993
- aggregateId;
1105
+ identity;
994
1106
  expectedSchemaVersion;
995
1107
  actualSchemaVersion;
996
1108
  constructor(options) {
997
1109
  super({
998
1110
  code: "SNAPSHOT_SCHEMA_MISMATCH",
999
- message: `Snapshot schema mismatch on ${options.aggregateType}(${options.aggregateId}): the snapshot model expects schema ${options.expectedSchemaVersion}, the stored snapshot carries ${options.actualSchemaVersion}. Override the model's migrate function to upgrade old snapshots, or discard the snapshot and refold from the full event stream.`
1111
+ message: `Snapshot schema mismatch on ${describeAggregateIdentity(options.identity)}: the snapshot model expects schema ${options.expectedSchemaVersion}, the stored snapshot carries ${options.actualSchemaVersion}. Override the model's migrate function to upgrade old snapshots, or discard the snapshot and refold from the full event stream.`
1000
1112
  });
1001
- this.aggregateType = options.aggregateType;
1002
- this.aggregateId = options.aggregateId;
1113
+ this.identity = detachAggregateIdentity(options.identity);
1003
1114
  this.expectedSchemaVersion = options.expectedSchemaVersion;
1004
1115
  this.actualSchemaVersion = options.actualSchemaVersion;
1005
1116
  }
@@ -1024,8 +1135,9 @@ var SnapshotSchemaMismatchError = class extends InfrastructureError {
1024
1135
  * but `version_unchanged`, which names a defect of the adapter.
1025
1136
  */
1026
1137
  var ConcurrencyConflictError = class extends InfrastructureError {
1027
- aggregateType;
1028
- aggregateId;
1138
+ identity;
1139
+ /** The write that failed, or `null` when the raising code does not know it. */
1140
+ intent;
1029
1141
  expectedVersion;
1030
1142
  /** The stored version, or `null` when none exists to name. */
1031
1143
  actualVersion;
@@ -1037,15 +1149,42 @@ var ConcurrencyConflictError = class extends InfrastructureError {
1037
1149
  cause: options.cause,
1038
1150
  retryable: options.reason !== "version_unchanged"
1039
1151
  });
1040
- this.aggregateType = options.aggregateType;
1041
- this.aggregateId = options.aggregateId;
1152
+ this.identity = detachAggregateIdentity(options.identity);
1153
+ this.intent = options.intent ?? null;
1042
1154
  this.expectedVersion = options.expectedVersion;
1043
1155
  this.actualVersion = options.actualVersion ?? null;
1044
1156
  this.reason = options.reason;
1045
1157
  }
1046
1158
  };
1159
+ /**
1160
+ * Sets the write intent on a conflict that its raising code could not name,
1161
+ * and renders the message again. The conflict keeps its class, its fields,
1162
+ * its cause, and its throw site.
1163
+ */
1164
+ function attributeConflictIntent(conflict, intent) {
1165
+ if (conflict.intent === intent) return;
1166
+ Reflect.set(conflict, "intent", intent);
1167
+ rewriteErrorMessage(conflict, concurrencyConflictMessage({
1168
+ identity: conflict.identity,
1169
+ intent,
1170
+ expectedVersion: conflict.expectedVersion,
1171
+ reason: conflict.reason,
1172
+ actualVersion: conflict.actualVersion
1173
+ }));
1174
+ }
1175
+ /**
1176
+ * Replaces the message of an error, and the message in the first line of its
1177
+ * stack where the engine lets the kit write it.
1178
+ */
1179
+ function rewriteErrorMessage(error, message) {
1180
+ const header = `${error.name}: ${error.message}`;
1181
+ const stack = Reflect.get(error, "stack");
1182
+ Reflect.set(error, "message", message);
1183
+ if (typeof stack === "string" && stack.startsWith(header)) Reflect.set(error, "stack", `${error.name}: ${message}${stack.slice(header.length)}`);
1184
+ }
1047
1185
  function concurrencyConflictMessage(options) {
1048
- const site = `${options.aggregateType}(${options.aggregateId})`;
1186
+ const aggregate = describeAggregateIdentity(options.identity);
1187
+ const site = options.intent == null ? aggregate : `${options.intent} of ${aggregate}`;
1049
1188
  switch (options.reason) {
1050
1189
  case "stale_version": return `Concurrency conflict on ${site}: expected version ${options.expectedVersion}, stored version ${options.actualVersion}.`;
1051
1190
  case "version_unchanged": return `Concurrency conflict on ${site}: the write matched nothing, although the stored version is ${options.actualVersion}. Its statement carries a condition beyond the version, or its version read answered from a transaction snapshot. Drop the extra condition, or read the version with a locking read, for example SELECT ... FOR SHARE.`;
@@ -1161,5 +1300,5 @@ var IdempotencyCompletionWithoutClaimError = class extends KitWiringError {
1161
1300
  };
1162
1301
 
1163
1302
  //#endregion
1164
- export { NonProgressingEventStreamPageError as A, SnapshotSchemaMismatchError as B, InvalidIntegrationMessageError as C, MissingEntityIdError as D, MisaddressedEventError as E, ProjectionOrderViolationError as F, UnprojectableEventError as G, UnenrolledChangesError as H, ProjectionReceiptViolationError as I, assertNoHostileOwnProtoKey as J, UnregisteredHandlerError as K, ReentrantEventRecordingError as L, PendingEventLimitExceededError as M, ProjectionGapError as N, MissingFoldError as O, ProjectionIdentityViolationError as P, ReplayHeadMismatchError as R, InvalidCommandMessageError as S, KitWiringError as T, UnmanagedInstanceError as U, SnapshotVersionNotRestoredError as V, UnmintedEventError as W, isInfrastructureErrorLike as X, isDomainErrorLike as Y, isWiringErrorLike as Z, IdempotencyInFlightError as _, DirectStateMutationError as a, InMemoryCapacityExceededError as b, DuplicateEventIdError as c, EventHarvestError as d, FoldReturnedNoStateError as f, IdempotencyCompletionWithoutClaimError as g, IdempotencyClaimLostError as h, ConcurrencyConflictError as i, PendingEventBatchMismatchError as j, MissingHandlerError as k, DuplicateHandlerRegistrationError as l, HostileStateKeyError as m, AggregateNotFoundError as n, DomainError as o, ForeignEventError as p, UnreplayableAggregateError as q, CapabilityRegistryConflictError as r, DuplicateAggregateError as s, AggregateDeletedError as t, ErrorMapperFailedError as u, IdempotencyKeyReuseError as v, InvalidVersionError as w, InfrastructureError as x, IdempotencyReconciliationRequiredError as y, SnapshotCorruptedError as z };
1303
+ export { isDomainErrorLike as $, MissingHandlerError as A, SnapshotCorruptedError as B, InvalidEventStreamPageError as C, MisattributedEventError as D, KitWiringError as E, ProjectionOrderViolationError as F, UnmintedEventError as G, SnapshotVersionNotRestoredError as H, ProjectionReceiptViolationError as I, UnreplayableAggregateError as J, UnprojectableEventError as K, ReentrantEventRecordingError as L, PendingEventLimitExceededError as M, ProjectionGapError as N, MissingEntityIdError as O, ProjectionIdentityViolationError as P, detachAggregateIdentity as Q, ReplayRejectedError as R, InvalidCommandMessageError as S, InvalidVersionError as T, UnenrolledChangesError as U, SnapshotSchemaMismatchError as V, UnmanagedInstanceError as W, attributeConflictIntent as X, assertNoHostileOwnProtoKey as Y, describeAggregateIdentity as Z, IdempotencyInFlightError as _, DirectStateMutationError as a, InMemoryCapacityExceededError as b, DuplicateEventIdError as c, EventHarvestError as d, isInfrastructureErrorLike as et, FoldReturnedNoStateError as f, IdempotencyCompletionWithoutClaimError as g, IdempotencyClaimLostError as h, ConcurrencyConflictError as i, PendingEventBatchMismatchError as j, MissingFoldError as k, DuplicateHandlerRegistrationError as l, HostileStateKeyError as m, AggregateNotFoundError as n, rewriteErrorMessage as nt, DomainError as o, ForeignEventError as p, UnregisteredHandlerError as q, CapabilityRegistryConflictError as r, DuplicateAggregateError as s, AggregateDeletedError as t, isWiringErrorLike as tt, ErrorMapperFailedError as u, IdempotencyKeyReuseError as v, InvalidIntegrationMessageError as w, InfrastructureError as x, IdempotencyReconciliationRequiredError as y, ReplayTargetMismatchError as z };
1165
1304
  //# sourceMappingURL=kit-errors.js.map