@shirudo/ddd-kit 3.0.0-rc.10 → 3.0.0-rc.12
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/chunks/kit-errors.d.ts +287 -108
- package/dist/chunks/kit-errors.js +250 -111
- package/dist/chunks/kit-errors.js.map +1 -1
- package/dist/chunks/{ports.js → read-stream-pages.js} +713 -225
- package/dist/chunks/read-stream-pages.js.map +1 -0
- package/dist/chunks/snapshot-store.d.ts +597 -115
- package/dist/index.d.ts +608 -437
- package/dist/index.js +1508 -637
- package/dist/index.js.map +1 -1
- package/dist/money.d.ts +1 -1
- package/dist/testing.d.ts +106 -15
- package/dist/testing.js +546 -52
- package/dist/testing.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunks/ports.js.map +0 -1
|
@@ -279,16 +279,16 @@ var FoldReturnedNoStateError = class extends KitWiringError {
|
|
|
279
279
|
* and a handler.
|
|
280
280
|
*/
|
|
281
281
|
var DirectStateMutationError = class extends KitWiringError {
|
|
282
|
-
|
|
283
|
-
constructor(
|
|
284
|
-
super("DIRECT_STATE_MUTATION", `Aggregate ${
|
|
285
|
-
this.
|
|
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
|
-
*
|
|
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
|
-
|
|
521
|
-
constructor(
|
|
522
|
-
super("UNREPLAYABLE_AGGREGATE", `Cannot replay onto aggregate ${
|
|
523
|
-
this.
|
|
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
|
-
/**
|
|
527
|
-
|
|
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
|
|
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
|
|
536
|
-
* recorded and poison the own stream. Events with MISSING
|
|
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
|
|
544
|
-
/**
|
|
572
|
+
var MisattributedEventError = class extends KitWiringError {
|
|
573
|
+
/** Identity of the aggregate that received the event. */
|
|
545
574
|
expected;
|
|
546
|
-
/**
|
|
575
|
+
/** Aggregate identity fields the event carries. */
|
|
547
576
|
actual;
|
|
548
577
|
eventType;
|
|
549
578
|
constructor(options) {
|
|
550
|
-
super("
|
|
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
|
-
|
|
566
|
-
aggregateId;
|
|
594
|
+
identity;
|
|
567
595
|
snapshotVersion;
|
|
568
596
|
restoredVersion;
|
|
569
597
|
constructor(options) {
|
|
570
|
-
super("SNAPSHOT_VERSION_NOT_RESTORED", `SnapshotModel.reconstitute for ${options.
|
|
571
|
-
this.
|
|
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
|
-
|
|
630
|
-
|
|
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(
|
|
647
|
-
super("DUPLICATE_EVENT_ID", `Two pending events of aggregate ${
|
|
648
|
-
this.
|
|
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
|
-
|
|
662
|
-
aggregateId;
|
|
693
|
+
identity;
|
|
663
694
|
limit;
|
|
664
695
|
pending;
|
|
665
696
|
added;
|
|
666
697
|
constructor(options) {
|
|
667
|
-
super("PENDING_EVENT_LIMIT_EXCEEDED", `Aggregate ${
|
|
668
|
-
this.
|
|
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
|
-
|
|
715
|
+
identity;
|
|
686
716
|
batchLength;
|
|
687
717
|
pendingLength;
|
|
688
|
-
constructor(
|
|
689
|
-
super("PENDING_EVENT_BATCH_MISMATCH", `The committed batch of ${batchLength} event(s) is no longer the pending prefix of aggregate ${
|
|
690
|
-
this.
|
|
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
|
|
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
|
|
738
|
+
* optional aggregate identity fields pass unchecked (the fields are optional on
|
|
709
739
|
* the event shape); new events are covered by
|
|
710
|
-
* {@link
|
|
740
|
+
* {@link MisattributedEventError}.
|
|
711
741
|
*/
|
|
712
742
|
var ForeignEventError = class extends InfrastructureError {
|
|
713
|
-
/**
|
|
743
|
+
/** Identity of the aggregate that received the event. */
|
|
714
744
|
expected;
|
|
715
|
-
/**
|
|
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 ${
|
|
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
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
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
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
* adapter and
|
|
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
|
|
740
|
-
|
|
741
|
-
|
|
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: "
|
|
747
|
-
message:
|
|
815
|
+
code: "INVALID_EVENT_STREAM_PAGE",
|
|
816
|
+
message: eventStreamPageReasonMessage(stream, options)
|
|
748
817
|
});
|
|
749
|
-
this.
|
|
750
|
-
this.
|
|
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
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
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
|
-
*
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
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
|
|
769
|
-
|
|
770
|
-
|
|
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: "
|
|
776
|
-
message
|
|
863
|
+
code: "REPLAY_TARGET_MISMATCH",
|
|
864
|
+
message
|
|
777
865
|
});
|
|
778
|
-
this.
|
|
779
|
-
this.
|
|
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
|
-
|
|
943
|
-
constructor(
|
|
944
|
-
super("UNENROLLED_CHANGES", `Aggregate ${
|
|
945
|
-
this.
|
|
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
|
-
|
|
960
|
-
constructor(
|
|
961
|
-
super("AGGREGATE_DELETED", `Aggregate ${
|
|
962
|
-
this.
|
|
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
|
-
|
|
967
|
-
id;
|
|
1083
|
+
identity;
|
|
968
1084
|
constructor(options) {
|
|
969
1085
|
super({
|
|
970
1086
|
code: "AGGREGATE_NOT_FOUND",
|
|
971
|
-
message: `Aggregate not found: ${
|
|
1087
|
+
message: `Aggregate not found: ${describeAggregateIdentity(options.identity)}`,
|
|
972
1088
|
cause: options.cause
|
|
973
1089
|
});
|
|
974
|
-
this.
|
|
975
|
-
this.id = options.id;
|
|
1090
|
+
this.identity = detachAggregateIdentity(options.identity);
|
|
976
1091
|
}
|
|
977
1092
|
};
|
|
978
1093
|
var DuplicateAggregateError = class extends InfrastructureError {
|
|
979
|
-
|
|
980
|
-
aggregateId;
|
|
1094
|
+
identity;
|
|
981
1095
|
constructor(options) {
|
|
982
1096
|
super({
|
|
983
1097
|
code: "DUPLICATE_AGGREGATE",
|
|
984
|
-
message: `Duplicate aggregate: ${
|
|
1098
|
+
message: `Duplicate aggregate: ${describeAggregateIdentity(options.identity)} already exists`,
|
|
985
1099
|
cause: options.cause
|
|
986
1100
|
});
|
|
987
|
-
this.
|
|
988
|
-
this.aggregateId = options.aggregateId;
|
|
1101
|
+
this.identity = detachAggregateIdentity(options.identity);
|
|
989
1102
|
}
|
|
990
1103
|
};
|
|
991
1104
|
var SnapshotSchemaMismatchError = class extends InfrastructureError {
|
|
992
|
-
|
|
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 ${
|
|
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.
|
|
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
|
-
|
|
1028
|
-
|
|
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.
|
|
1041
|
-
this.
|
|
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
|
|
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 {
|
|
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
|