@shirudo/ddd-kit 3.0.0-rc.12 → 3.0.0-rc.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { $ as isDomainErrorLike, A as MissingHandlerError, B as SnapshotCorruptedError, C as InvalidEventStreamPageError, D as MisattributedEventError, E as KitWiringError, F as ProjectionOrderViolationError, G as UnmintedEventError, H as SnapshotVersionNotRestoredError, I as ProjectionReceiptViolationError, J as UnreplayableAggregateError, K as UnprojectableEventError, L as ReentrantEventRecordingError, M as PendingEventLimitExceededError, N as ProjectionGapError, O as MissingEntityIdError, P as ProjectionIdentityViolationError, Q as detachAggregateIdentity, R as ReplayRejectedError, S as InvalidCommandMessageError, T as InvalidVersionError, U as UnenrolledChangesError, V as SnapshotSchemaMismatchError, W as UnmanagedInstanceError, X as attributeConflictIntent, Y as assertNoHostileOwnProtoKey, Z as describeAggregateIdentity, _ as IdempotencyInFlightError, a as DirectStateMutationError, b as InMemoryCapacityExceededError, c as DuplicateEventIdError, d as EventHarvestError, et as isInfrastructureErrorLike, f as FoldReturnedNoStateError, g as IdempotencyCompletionWithoutClaimError, h as IdempotencyClaimLostError, i as ConcurrencyConflictError, j as PendingEventBatchMismatchError, k as MissingFoldError, l as DuplicateHandlerRegistrationError, m as HostileStateKeyError, n as AggregateNotFoundError, nt as rewriteErrorMessage, o as DomainError, p as ForeignEventError, q as UnregisteredHandlerError, r as CapabilityRegistryConflictError, s as DuplicateAggregateError, t as AggregateDeletedError, tt as isWiringErrorLike, u as ErrorMapperFailedError, v as IdempotencyKeyReuseError, w as InvalidIntegrationMessageError, x as InfrastructureError, y as IdempotencyReconciliationRequiredError, z as ReplayTargetMismatchError } from "./chunks/kit-errors.js";
2
- import { A as SnapshotTimeValidationError, B as stampCooperativeBrand, C as defaultDomainEventFactory, D as mintUncommittedDomainEvent, E as mergeMetadata, F as vo, G as isBuiltInObject, H as deepOmit, I as voEquals, K as isIntrinsicConstructorPrototype, L as voEqualsExcept, M as detachState, N as ValueObject, O as recordDomainEvent, P as deepFreeze, R as voWithValidation, S as createUncommittedDomainEvent, T as isUncommittedDomainEvent, U as deepEqual, V as deepEqualExcept, W as findPropertyDescriptor, _ as adoptUncommittedDomainEvent, a as isDispatchTrackingOutbox, b as createDomainEventFactory, c as runBoundedExecution, d as assertPositiveInteger, f as assertPositiveSafeInteger, g as adoptRecordedDomainEvent, h as waitRejectingOnAbort, i as assertPageWithinWindow, j as detachFreezableState, k as DomainEventValidationError, l as MAX_TIMER_DELAY_MS, m as abortReason, n as readStreamPages, o as DEFAULT_EXECUTION_TIMEOUT_MS, p as assertTimerDelay, q as isWeakMap, r as assertPageNotEmpty, s as ownerSignalOf, t as pinTargetVersion, u as assertNonNegativeSafeInteger, v as copyMetadata, w as isRecordedDomainEvent, x as createDomainEventFromFacts, y as createDomainEvent, z as hasCooperativeBrand } from "./chunks/read-stream-pages.js";
1
+ import { $ as isDomainErrorLike, A as MissingHandlerError, B as ReplayTargetMismatchError, C as InvalidEventStreamPageError, D as MisattributedEventError, E as KitWiringError, F as ProjectionOrderViolationError, G as UnprojectableEventError, H as SnapshotSchemaMismatchError, I as ProjectionReceiptViolationError, J as UnreplayableAggregateError, K as UnregisteredChangesError, L as PrototypePollutionError, M as PendingEventLimitExceededError, N as ProjectionGapError, O as MissingEntityIdError, P as ProjectionIdentityViolationError, Q as describeAggregateIdentity, R as ReentrantEventRecordingError, S as InvalidCommandMessageError, T as InvalidVersionError, U as SnapshotVersionNotRestoredError, V as SnapshotCorruptedError, W as UnmanagedInstanceError, X as attributeConflictIntent, Y as assertNoOwnProtoKey, Z as copyAggregateIdentity, _ as IdempotencyInFlightError, a as DirectStateMutationError, b as InMemoryCapacityExceededError, c as DuplicateEventIdError, d as EventCollectionError, et as isInfrastructureErrorLike, f as FoldReturnedNoStateError, g as IdempotencyCompletionWithoutClaimError, h as IdempotencyClaimLostError, i as ConcurrencyConflictError, j as PendingEventBatchMismatchError, k as MissingFoldError, l as DuplicateHandlerRegistrationError, m as HandBuiltEventError, n as AggregateNotFoundError, nt as rewriteErrorMessage, o as DomainError, p as ForeignEventError, q as UnregisteredHandlerError, r as CapabilityRegistryConflictError, s as DuplicateAggregateError, t as AggregateDeletedError, tt as isWiringErrorLike, u as ErrorMapperFailedError, v as IdempotencyKeyReuseError, w as InvalidIntegrationMessageError, x as InfrastructureError, y as IdempotencyReconciliationRequiredError, z as ReplayRejectedError } from "./chunks/kit-errors.js";
2
+ import { a as valueObjectEqualsExcept, c as applyCooperativeBrand, d as deepEqual, i as valueObjectEquals, l as hasCooperativeBrand, n as deepFreeze, o as valueObjectOrError, r as valueObject, t as ValueObject, u as deepOmit, v as isWeakMap } from "./chunks/value-object.js";
3
+ import { A as copyFreezableState, C as defaultDomainEventFactory, D as stampDomainEvent, E as mergeMetadata, O as DomainEventValidationError, S as createStampedDomainEvent, T as isUnstampedDomainEvent, _ as adoptUnstampedDomainEvent, a as isDispatchTrackingOutbox, b as createDomainEvent, c as runBoundedExecution, d as assertPositiveInteger, f as assertPositiveSafeInteger, g as adoptStampedDomainEvent, h as waitRejectingOnAbort, i as assertPageWithinWindow, j as copyPlainState, k as SnapshotTimeValidationError, l as MAX_TIMER_DELAY_MS, m as abortReason, n as readStreamPages, o as DEFAULT_EXECUTION_TIMEOUT_MS, p as assertTimerDelay, r as assertPageNotEmpty, s as ownerSignalOf, t as pinTargetVersion, u as assertNonNegativeSafeInteger, v as buildUnstampedDomainEvent, w as isStampedDomainEvent, x as createDomainEventFactory, y as copyMetadata } from "./chunks/read-stream-pages.js";
3
4
  import { err, ok } from "@shirudo/result";
4
5
  import { ValidationError, someChainRetryable } from "@shirudo/base-error";
5
6
 
@@ -403,40 +404,76 @@ function captureObserverFunctions(context, observers, required) {
403
404
  return Object.freeze(captured);
404
405
  }
405
406
 
407
+ //#endregion
408
+ //#region src/domain/aggregate/pending-event-recording.ts
409
+ const { registry: capabilities$1, require } = createGlobalCapabilityRegistry(Symbol.for("@shirudo/ddd-kit/pending-event-recording-registry/v2"));
410
+ /** Resolves the recording capability or throws `UnmanagedInstanceError`. */
411
+ function requirePendingEventRecordingCapability(aggregate, operation) {
412
+ return require(aggregate, operation, "aggregate");
413
+ }
414
+ function registerPendingEventRecordingCapability(aggregate, capability) {
415
+ capabilities$1.set(aggregate, Object.freeze(capability));
416
+ }
417
+
418
+ //#endregion
419
+ //#region src/application/unit-of-work/stamp-pending-events.ts
420
+ function stampPendingEvents(aggregate, source, stampOptions) {
421
+ return recordWith(requirePendingEventRecordingCapability(aggregate, "stampPendingEvents"), source, stampOptions);
422
+ }
423
+ /**
424
+ * Stamps the pending events that are still unstamped when the commit
425
+ * registers an aggregate. An aggregate whose pending events all carry a
426
+ * stamp stays untouched: an explicit {@link stampPendingEvents} call keeps
427
+ * its stamps, and an instance without the recording capability passes when
428
+ * it has nothing to stamp.
429
+ */
430
+ function stampAtRegistration(aggregate, factory, stampOptions, operation) {
431
+ if (aggregate.pendingEvents.every(isStampedDomainEvent)) return;
432
+ recordWith(requirePendingEventRecordingCapability(aggregate, operation), factory, stampOptions);
433
+ }
434
+ function recordWith(capability, source, stampOptions) {
435
+ const shared = {
436
+ occurredAt: stampOptions?.occurredAt,
437
+ metadata: stampOptions?.metadata
438
+ };
439
+ const createStamp = typeof source === "function" ? source : () => source.createStamp(shared);
440
+ return capability.record((event, index) => createStamp(event, index));
441
+ }
442
+
406
443
  //#endregion
407
444
  //#region src/application/cqrs/handler.ts
408
445
  /**
409
446
  * True when the aggregate's live version or pending-event batch no longer
410
- * matches its enrollment-time snapshot. Shared by the duplicate-enrollment
411
- * gate and the harvest-time recheck: both must reject the same divergence,
412
- * or events recorded after enrollment would be silently dropped.
447
+ * matches its registration-time snapshot. Shared by the duplicate-registration
448
+ * gate and the collection-time recheck: both must reject the same divergence,
449
+ * or events recorded after registration would be silently dropped.
413
450
  */
414
- function enrollmentDiverged(record) {
451
+ function registrationDiverged(record) {
415
452
  const pending = record.aggregate.pendingEvents;
416
453
  return record.aggregate.version !== record.version || pending.length !== record.events.length || record.events.some((event, index) => event !== pending[index]);
417
454
  }
418
455
  /** One token registry per transactional callback attempt. */
419
- function createCommitTokenScope() {
456
+ function createCommitTokenScope(stamp) {
420
457
  const recordsByToken = /* @__PURE__ */ new WeakMap();
421
458
  const tokensByAggregate = /* @__PURE__ */ new WeakMap();
422
- let mintedTokenCount = 0;
459
+ let issuedTokenCount = 0;
423
460
  let open = true;
424
- const enroll = (aggregate, disposition, options) => {
425
- if (!open) throw new EventHarvestError("withCommit: commit enrollment was used after its work callback settled. Await every repository write and return its token before leaving the callback.");
461
+ const register = (aggregate, disposition, options) => {
462
+ if (!open) throw new EventCollectionError("withCommit: commit registration was used after its work callback settled. Await every repository write and return its token before leaving the callback.");
426
463
  const existing = tokensByAggregate.get(aggregate);
427
464
  if (existing) {
428
465
  const record = recordsByToken.get(existing);
429
- if (!record) throw new EventHarvestError("withCommit: internal commit-token registry is inconsistent.");
430
- if (record.disposition === "deleted" && disposition === "saved") throw new EventHarvestError(`withCommit: aggregate ${describeAggregateIdentity(aggregate.aggregateIdentity)} was enrolled as saved after it was enrolled as deleted in the same transaction.`);
431
- if (options?.expectedVersion !== void 0 && options.expectedVersion !== record.expectedVersion) throw new EventHarvestError(`withCommit: aggregate ${describeAggregateIdentity(aggregate.aggregateIdentity)} was re-enrolled with expectedVersion ${String(options.expectedVersion)}, but its enrollment recorded ${String(record.expectedVersion)}. Duplicate enrollment must assert the same OCC baseline or none.`);
432
- if (enrollmentDiverged(record)) throw new EventHarvestError(`withCommit: aggregate ${describeAggregateIdentity(aggregate.aggregateIdentity)} changed after its commit batch was enrolled. Register persistence intent last.`);
466
+ if (!record) throw new EventCollectionError("withCommit: internal commit-token registry is inconsistent.");
467
+ if (record.disposition === "deleted" && disposition === "saved") throw new EventCollectionError(`withCommit: aggregate ${describeAggregateIdentity(aggregate.aggregateIdentity)} was registered as saved after it was registered as deleted in the same transaction.`);
468
+ if (options?.expectedVersion !== void 0 && options.expectedVersion !== record.expectedVersion) throw new EventCollectionError(`withCommit: aggregate ${describeAggregateIdentity(aggregate.aggregateIdentity)} was re-registered with expectedVersion ${String(options.expectedVersion)}, but its registration recorded ${String(record.expectedVersion)}. Duplicate registration must assert the same OCC baseline or none.`);
469
+ if (registrationDiverged(record)) throw new EventCollectionError(`withCommit: aggregate ${describeAggregateIdentity(aggregate.aggregateIdentity)} changed after its commit batch was registered. Register persistence intent last.`);
433
470
  if (disposition === "deleted") record.disposition = "deleted";
434
471
  return existing;
435
472
  }
436
- const eventLifecycle = requirePendingEventLifecycleCapability(aggregate, "withCommit enrollment");
473
+ const eventLifecycle = requirePendingEventLifecycleCapability(aggregate, "withCommit registration");
437
474
  const token = Object.freeze(Object.create(null));
475
+ stamp(aggregate, "withCommit registration");
438
476
  const events = aggregate.pendingEvents;
439
- for (const event of events) if (!isRecordedDomainEvent(event)) throw new EventHarvestError(`withCommit: event "${event.type}" has not been recorded. Call recordPendingEvents(aggregate, createStamp) in the application shell before persistence or outbox harvest.`, event.type);
440
477
  const persistedVersion = eventLifecycle.persistedVersion();
441
478
  tokensByAggregate.set(aggregate, token);
442
479
  recordsByToken.set(token, {
@@ -448,32 +485,32 @@ function createCommitTokenScope() {
448
485
  persistedVersion,
449
486
  events
450
487
  });
451
- mintedTokenCount += 1;
488
+ issuedTokenCount += 1;
452
489
  return token;
453
490
  };
454
491
  return {
455
- enrollment: Object.freeze({
456
- enrollSaved: (aggregate, options) => enroll(aggregate, "saved", options),
457
- enrollDeleted: (aggregate, options) => enroll(aggregate, "deleted", options)
492
+ registration: Object.freeze({
493
+ registerSaved: (aggregate, options) => register(aggregate, "saved", options),
494
+ registerRemoved: (aggregate, options) => register(aggregate, "deleted", options)
458
495
  }),
459
496
  close: () => {
460
497
  open = false;
461
498
  },
462
499
  resolve: (tokens) => {
463
- if (!Array.isArray(tokens)) throw new EventHarvestError("withCommit: the work callback must return `commits` containing tokens from the current enrollment capability. Naked aggregate arrays are not commit evidence.");
500
+ if (!Array.isArray(tokens)) throw new EventCollectionError("withCommit: the work callback must return `commits` containing tokens from the current registration capability. Naked aggregate arrays are not commit evidence.");
464
501
  const seen = /* @__PURE__ */ new Set();
465
502
  const records = [];
466
503
  for (const token of tokens) {
467
- if (token === null || typeof token !== "object" && typeof token !== "function") throw new EventHarvestError("withCommit: a commit token was not minted by this callback's enrollment capability. Forged and stale tokens are rejected.");
504
+ if (token === null || typeof token !== "object" && typeof token !== "function") throw new EventCollectionError("withCommit: a commit token was not issued by this callback's registration capability. Forged and stale tokens are rejected.");
468
505
  const tokenObject = token;
469
506
  const record = recordsByToken.get(tokenObject);
470
- if (!record) throw new EventHarvestError("withCommit: a commit token was not minted by this callback's enrollment capability. Forged and stale tokens are rejected.");
507
+ if (!record) throw new EventCollectionError("withCommit: a commit token was not issued by this callback's registration capability. Forged and stale tokens are rejected.");
471
508
  if (seen.has(tokenObject)) continue;
472
509
  seen.add(tokenObject);
473
- if (enrollmentDiverged(record)) throw new EventHarvestError(`withCommit: aggregate ${describeAggregateIdentity(record.aggregate.aggregateIdentity)} changed after its commit batch was enrolled; events recorded after enrollment are not part of the attested write and would be silently dropped. Make domain decisions first, write, and enroll last.`);
510
+ if (registrationDiverged(record)) throw new EventCollectionError(`withCommit: aggregate ${describeAggregateIdentity(record.aggregate.aggregateIdentity)} changed after its commit batch was registered; events recorded after registration are not part of the attested write and would be silently dropped. Make domain decisions first, write, and register last.`);
474
511
  records.push(record);
475
512
  }
476
- if (seen.size !== mintedTokenCount) throw new EventHarvestError("withCommit: every token minted by the current enrollment capability must be returned in `commits`. If an enrolled write must not commit, throw so the transaction rolls back.");
513
+ if (seen.size !== issuedTokenCount) throw new EventCollectionError("withCommit: every token issued by the current registration capability must be returned in `commits`. If a registered write must not commit, throw so the transaction rolls back.");
477
514
  return records;
478
515
  }
479
516
  };
@@ -481,29 +518,29 @@ function createCommitTokenScope() {
481
518
  /**
482
519
  * Helper for executing a write Use Case inside a transaction scope.
483
520
  *
484
- * The use-case callback receives an invocation-scoped enrollment capability
521
+ * The use-case callback receives an invocation-scoped registration capability
485
522
  * and returns opaque commit tokens for the repository writes that completed
486
- * in the transaction. `withCommit` owns the post-commit lifecycle (harvest,
523
+ * in the transaction. `withCommit` owns the post-commit lifecycle (collection,
487
524
  * outbox, mark-persisted, publish). A naked aggregate is not commit evidence:
488
525
  * merely touching or constructing one must never make it look persisted.
489
526
  *
490
- * **Trust boundary.** A token proves invocation-local enrollment, not that the
527
+ * **Trust boundary.** A token proves invocation-local registration, not that the
491
528
  * kit inspected a database write; a generic transaction helper cannot observe
492
- * adapter internals. Repository code must enroll only writes participating in
529
+ * adapter internals. Repository code must register only writes participating in
493
530
  * this transaction. `UnitOfWork` centralizes that rule in repository methods.
494
531
  * The opaque, scoped token prevents accidental aggregate smuggling and stale
495
532
  * reuse; it is not a security boundary against code that deliberately lies to
496
533
  * its own persistence capability.
497
534
  *
498
535
  * Order of operations:
499
- * 1. `fn(ctx, enrollment)` runs inside `scope.transactional(...)`; domain
536
+ * 1. `fn(ctx, registration)` runs inside `scope.transactional(...)`; domain
500
537
  * mutations + repo writes happen here. After a repository write has
501
- * enrolled an aggregate, the callback includes that opaque token in its
538
+ * registered an aggregate, the callback includes that opaque token in its
502
539
  * `commits` result. Tokens are invocation-bound: forged or stale tokens
503
- * fail before harvest. `ctx` is whatever transaction handle the `scope`
540
+ * fail before collection. `ctx` is whatever transaction handle the `scope`
504
541
  * exposes (Drizzle `tx`, Prisma `tx`, Mongo session, or `undefined` for
505
542
  * context-free scopes).
506
- * 2. **Still inside the transaction**, `withCommit` harvests every
543
+ * 2. **Still inside the transaction**, `withCommit` collects every
507
544
  * aggregate's `pendingEvents` and writes them via `outbox.add` (so
508
545
  * events persist atomically with the state change). Skipped when no
509
546
  * events were recorded. Each bare domain event is composed into an
@@ -513,7 +550,7 @@ function createCommitTokenScope() {
513
550
  * resulting `CommittedDomainEvent`. The domain event itself is never
514
551
  * stamped or copied.
515
552
  *
516
- * **Harvest order.** Events are concatenated in the order
553
+ * **Collection order.** Events are concatenated in the order
517
554
  * tokens appear in the returned `commits` array, then in
518
555
  * each aggregate's `pendingEvents` order (insertion order via
519
556
  * `apply` / `commit` / `addDomainEvent`). So tokens for `[a, b]`
@@ -537,7 +574,7 @@ function createCommitTokenScope() {
537
574
  * across aggregates at delivery time.
538
575
  * 3. The transaction commits.
539
576
  * 4. **After** the commit, a non-exported capability acknowledges every
540
- * saved enrollment and discards pending events for deleted enrollments.
577
+ * saved registration and discards pending events for deleted registrations.
541
578
  * Only after the complete commit set is clean does the optional
542
579
  * application-shell `onPersisted(aggregate, version, context)` observer run for
543
580
  * saved aggregates. Deleted rows never trigger that observer.
@@ -567,16 +604,16 @@ function createCommitTokenScope() {
567
604
  * If the transaction rolls back, no acknowledgement occurs: the aggregate
568
605
  * keeps its pending events, so the caller can retry or discard the instance.
569
606
  *
570
- * Enrollment captures an exact version and event batch. Re-enrolling the same
607
+ * Registration captures an exact version and event batch. Re-registering the same
571
608
  * aggregate after it changes rejects. `UnitOfWork` additionally seals the
572
- * adapter persistence projection and rejects later mutation before flush. For
573
- * direct `withCommit` use, make domain decisions first, write, and enroll last.
609
+ * adapter persistence capture and rejects later mutation before flush. For
610
+ * direct `withCommit` use, make domain decisions first, write, and register last.
574
611
  *
575
- * **Duplicate enrollment is idempotent by reference.** Enrolling the same
612
+ * **Duplicate registration is idempotent by reference.** Registering the same
576
613
  * instance repeatedly returns the same token, and a repeated token in
577
- * `commits` is harvested once. A repeat call that omits `expectedVersion`
614
+ * `commits` is collected once. A repeat call that omits `expectedVersion`
578
615
  * makes no OCC assertion; only a supplied value that contradicts the
579
- * enrollment-time baseline rejects. Each event lands in the outbox exactly once
616
+ * registration-time baseline rejects. Each event lands in the outbox exactly once
580
617
  * and post-commit acknowledgement runs exactly once. Two
581
618
  * *different* instances with the same logical id cannot be detected
582
619
  * at this layer; that is a Repository contract violation (failure to
@@ -587,19 +624,19 @@ function createCommitTokenScope() {
587
624
  *
588
625
  * @example Tx-bound repos (Drizzle, Prisma, Mongo, …)
589
626
  * ```typescript
590
- * const result = await withCommit({ outbox, bus, scope }, async (tx, enrollment) => {
627
+ * const result = await withCommit({ outbox, bus, scope }, async (tx, registration) => {
591
628
  * const orderRepository = makeOrderRepository(tx); // your factory binds tx to the repo
592
629
  * const order = await orderRepository.getById(orderId);
593
630
  * order.confirm();
594
631
  * await persistOrder(tx, order); // low-level adapter write
595
- * const commit = enrollment.enrollSaved(order); // attest the repository write
632
+ * const commit = registration.registerSaved(order); // attest the repository write
596
633
  * return { result: order.id, commits: [commit] };
597
634
  * });
598
635
  * ```
599
636
  */
600
637
  async function withCommit(deps, fn) {
601
- return withCheckedCommit(deps, async (ctx, enrollment) => {
602
- const { result, commits } = await fn(ctx, enrollment);
638
+ return withCheckedCommit(deps, async (ctx, registration) => {
639
+ const { result, commits } = await fn(ctx, registration);
603
640
  return {
604
641
  result,
605
642
  commits
@@ -608,46 +645,48 @@ async function withCommit(deps, fn) {
608
645
  }
609
646
  /**
610
647
  * {@link withCommit} with a check per attempt before the commit. The scope
611
- * receives `onAttemptStart` in its transactional options.
648
+ * receives `onAttemptStart` in its transactional options. The callback also
649
+ * receives the stamping step of the registration, so that a caller that
650
+ * records the batch before it registers can stamp first.
612
651
  */
613
652
  async function withCheckedCommit(deps, fn, onAttemptStart) {
614
653
  const postCommitTimeoutMs = deps.postCommitTimeoutMs ?? 3e4;
615
654
  assertTimerDelay("withCommit", "postCommitTimeoutMs", postCommitTimeoutMs);
616
655
  if (deps.signal?.aborted) throw abortReason(deps.signal, "withCommit aborted before opening a transaction");
656
+ const eventStampFactory = deps.eventStampFactory ?? defaultDomainEventFactory;
657
+ const stamp = (aggregate, operation) => stampAtRegistration(aggregate, eventStampFactory, deps.stampOptions, operation);
617
658
  const { result, commitRecords, events } = await deps.scope.transactional(async (ctx) => {
618
- const tokenScope = createCommitTokenScope();
659
+ const tokenScope = createCommitTokenScope(stamp);
619
660
  let fnResult;
620
661
  try {
621
- fnResult = await fn(ctx, tokenScope.enrollment);
662
+ fnResult = await fn(ctx, tokenScope.registration, stamp);
622
663
  } finally {
623
664
  tokenScope.close();
624
665
  }
625
666
  try {
626
667
  const commitRecords = tokenScope.resolve(fnResult.commits);
627
668
  const candidates = commitRecords.flatMap((record) => {
628
- const enrolled = record.aggregate.aggregateIdentity;
629
- if (record.events.length > 0 && record.persistedVersion !== void 0 && record.version <= record.persistedVersion) throw new EventHarvestError(`withCommit: aggregate ${describeAggregateIdentity(enrolled)} recorded events but did not advance its version beyond the persisted version (${String(record.persistedVersion)}). An eventful commit needs a unique cursor; use StateStoredAggregate.setState(currentState, event) instead of addDomainEvent(event) alone.`);
630
- const enrolledId = enrolled.aggregateId;
631
- const enrolledType = enrolled.aggregateType;
669
+ const registered = record.aggregate.aggregateIdentity;
670
+ if (record.events.length > 0 && record.persistedVersion !== void 0 && record.version <= record.persistedVersion) throw new EventCollectionError(`withCommit: aggregate ${describeAggregateIdentity(registered)} recorded events but did not advance its version beyond the persisted version (${String(record.persistedVersion)}). An eventful commit needs a unique cursor; use StateStoredAggregate.setState(currentState, event) instead of addDomainEvent(event) alone.`);
671
+ const registeredId = registered.aggregateId;
672
+ const registeredType = registered.aggregateType;
632
673
  return record.events.map((event, index) => {
633
- if (!isRecordedDomainEvent(event)) throw new EventHarvestError(`withCommit: event "${event.type}" has not been recorded. Call recordPendingEvents(aggregate, createStamp) in the application shell before persistence or outbox harvest.`, event.type);
634
- const recordedEvent = event;
635
674
  const commitSize = record.events.length;
636
- const aggregateId = recordedEvent.aggregateId;
637
- const aggregateType = recordedEvent.aggregateType;
675
+ const aggregateId = event.aggregateId;
676
+ const aggregateType = event.aggregateType;
638
677
  const missing = [];
639
678
  if (!aggregateId) missing.push("aggregateId");
640
679
  if (!aggregateType) missing.push("aggregateType");
641
- if (!aggregateId || !aggregateType) throw new EventHarvestError(`withCommit: event "${recordedEvent.type}" is missing ${missing.join(" and ")}. Use this.createEvent(type, payload) inside aggregate methods instead of createDomainEvent(...); createEvent auto-injects aggregateId and aggregateType. Outbox dispatchers and projection handlers rely on the envelope source.`, recordedEvent.type);
642
- if (aggregateId !== enrolledId || aggregateType !== enrolledType) throw new EventHarvestError(`withCommit: event "${recordedEvent.type}" belongs to ${describeAggregateIdentity({
680
+ if (!aggregateId || !aggregateType) throw new EventCollectionError(`withCommit: event "${event.type}" is missing ${missing.join(" and ")}. Use this.createEvent(type, payload) inside aggregate methods instead of createDomainEvent(...); createEvent auto-injects aggregateId and aggregateType. Outbox dispatchers and projection handlers rely on the envelope source.`, event.type);
681
+ if (aggregateId !== registeredId || aggregateType !== registeredType) throw new EventCollectionError(`withCommit: event "${event.type}" belongs to ${describeAggregateIdentity({
643
682
  aggregateType,
644
683
  aggregateId
645
- })} but was enrolled under ${describeAggregateIdentity({
646
- aggregateType: enrolledType,
647
- aggregateId: enrolledId
648
- })}. The aggregate base classes stamp the aggregate identity on every recording path; an instance from another package copy must stamp it the same way.`, recordedEvent.type);
684
+ })} but was registered under ${describeAggregateIdentity({
685
+ aggregateType: registeredType,
686
+ aggregateId: registeredId
687
+ })}. The aggregate base classes attribute every event to the aggregate on every recording path; an instance from another package copy must stamp it the same way.`, event.type);
649
688
  return Object.freeze({
650
- event: recordedEvent,
689
+ event,
651
690
  source: Object.freeze({
652
691
  aggregateId,
653
692
  aggregateType
@@ -660,12 +699,12 @@ async function withCheckedCommit(deps, fn, onAttemptStart) {
660
699
  });
661
700
  });
662
701
  });
663
- const endedSources = commitRecords.filter((record) => record.disposition === "deleted").map((record) => detachAggregateIdentity(record.aggregate.aggregateIdentity));
702
+ const endedSources = commitRecords.filter((record) => record.disposition === "deleted").map((record) => copyAggregateIdentity(record.aggregate.aggregateIdentity));
664
703
  if (candidates.length > 0) await deps.outbox.add(Object.freeze(candidates));
665
704
  if (endedSources.length > 0) await deps.outbox.endEventSources(Object.freeze(endedSources));
666
705
  fnResult.checkBeforeCommit?.();
667
706
  if (candidates.length > 0 || endedSources.length > 0) {
668
- for (const record of commitRecords) if (enrollmentDiverged(record)) throw new EventHarvestError(`withCommit: aggregate ${describeAggregateIdentity(record.aggregate.aggregateIdentity)} changed while its outbox write ran. Await every change inside the work callback.`);
707
+ for (const record of commitRecords) if (registrationDiverged(record)) throw new EventCollectionError(`withCommit: aggregate ${describeAggregateIdentity(record.aggregate.aggregateIdentity)} changed while its outbox write ran. Await every change inside the work callback.`);
669
708
  }
670
709
  return {
671
710
  result: fnResult.result,
@@ -1406,7 +1445,7 @@ function positiveSafeInteger(value) {
1406
1445
  *
1407
1446
  * On its own, the store is the reference for the leased family: it cannot
1408
1447
  * see commits or rollbacks. Claims and staged outcomes carry bounded leases,
1409
- * while every mutation compares the store-minted token. An expired pending
1448
+ * while every mutation compares the store-issued token. An expired pending
1410
1449
  * claim may be replaced; an expired staged outcome cannot be guessed away and
1411
1450
  * instead returns `reconciliation-required`. Only an authoritative
1412
1451
  * `committed` / `not-committed` decision can settle it.
@@ -1669,20 +1708,20 @@ function startLeaseHeartbeat(store, claim) {
1669
1708
  failure: () => heartbeatFailure
1670
1709
  };
1671
1710
  }
1672
- function scopeWorkEnrollment(parent) {
1711
+ function scopeWorkRegistration(parent) {
1673
1712
  let open = true;
1674
1713
  const assertOpen = () => {
1675
- if (!open) throw new EventHarvestError("withIdempotentCommit: commit enrollment was used after the user work callback settled. Await every repository write before returning from the callback.");
1714
+ if (!open) throw new EventCollectionError("withIdempotentCommit: commit registration was used after the user work callback settled. Await every repository write before returning from the callback.");
1676
1715
  };
1677
1716
  return {
1678
- enrollment: Object.freeze({
1679
- enrollSaved: (aggregate) => {
1717
+ registration: Object.freeze({
1718
+ registerSaved: (...args) => {
1680
1719
  assertOpen();
1681
- return parent.enrollSaved(aggregate);
1720
+ return parent.registerSaved(...args);
1682
1721
  },
1683
- enrollDeleted: (aggregate) => {
1722
+ registerRemoved: (...args) => {
1684
1723
  assertOpen();
1685
- return parent.enrollDeleted(aggregate);
1724
+ return parent.registerRemoved(...args);
1686
1725
  }
1687
1726
  }),
1688
1727
  close: () => {
@@ -1705,12 +1744,12 @@ function scopeWorkEnrollment(parent) {
1705
1744
  * 2. A fresh claim carries an opaque ownership token. For a leased store the
1706
1745
  * wrapper renews it at `renewAfterMs` until the transaction callback is
1707
1746
  * ready to commit. A renewal failure rejects before commit and releases
1708
- * the claim. `fn(ctx, enrollment, execution)` receives the same token so a
1747
+ * the claim. `fn(ctx, registration, execution)` receives the same token so a
1709
1748
  * source-side marker can make later reconciliation conclusive.
1710
1749
  * 3. `store.complete(ctx, claim, fn's result)` stages or completes the outcome
1711
- * in the same transaction as aggregate writes and outbox. The enrollment
1750
+ * in the same transaction as aggregate writes and outbox. The registration
1712
1751
  * capability is sealed and its token array copied before `complete` can
1713
- * yield, so leaked callback state cannot change the harvest receipt.
1752
+ * yield, so leaked callback state cannot change the collection receipt.
1714
1753
  * 4. After commit, `store.confirm(claim)` finalizes a leased store's staged
1715
1754
  * outcome; it is a no-op for transactional stores. A failure cannot reject
1716
1755
  * an already committed write, so it is sent to `onIdempotencyError` and the
@@ -1798,7 +1837,7 @@ async function withIdempotentCommit(deps, request, fn) {
1798
1837
  const outcome = await withCommit({
1799
1838
  ...deps,
1800
1839
  scope
1801
- }, async (ctx, enrollment) => {
1840
+ }, async (ctx, registration) => {
1802
1841
  let claim = await store.claim(ctx, request.key, request.fingerprint);
1803
1842
  if (claim.status === "reconciliation-required") {
1804
1843
  const decision = deps.reconcileIdempotency ? await deps.reconcileIdempotency(claim.reconciliation, ctx) : "unknown";
@@ -1817,16 +1856,16 @@ async function withIdempotentCommit(deps, request, fn) {
1817
1856
  };
1818
1857
  attempt.claim = claim.claim;
1819
1858
  attempt.heartbeat = startLeaseHeartbeat(store, claim.claim);
1820
- const workEnrollment = scopeWorkEnrollment(enrollment);
1859
+ const workRegistration = scopeWorkRegistration(registration);
1821
1860
  let work;
1822
1861
  try {
1823
- work = await fn(ctx, workEnrollment.enrollment, {
1862
+ work = await fn(ctx, workRegistration.registration, {
1824
1863
  key: request.key,
1825
1864
  fingerprint: request.fingerprint,
1826
1865
  claimToken: claim.claim.token
1827
1866
  });
1828
1867
  } finally {
1829
- workEnrollment.close();
1868
+ workRegistration.close();
1830
1869
  }
1831
1870
  const result = work.result;
1832
1871
  const commits = Array.isArray(work.commits) ? Object.freeze([...work.commits]) : work.commits;
@@ -1841,7 +1880,7 @@ async function withIdempotentCommit(deps, request, fn) {
1841
1880
  });
1842
1881
  if (!outcome.replayed) {
1843
1882
  const committedClaim = attempt.claim;
1844
- if (!committedClaim) throw new EventHarvestError("withIdempotentCommit: a fresh result committed without its claim receipt.");
1883
+ if (!committedClaim) throw new EventCollectionError("withIdempotentCommit: a fresh result committed without its claim receipt.");
1845
1884
  try {
1846
1885
  await store.confirm(committedClaim);
1847
1886
  } catch (confirmError) {
@@ -2438,7 +2477,7 @@ var RepositoryErrorMappingFailedError = class extends KitWiringError {
2438
2477
  mapperCause;
2439
2478
  constructor(options) {
2440
2479
  super("REPOSITORY_ERROR_MAPPING_FAILED", `The repository error mapper failed for ${options.intent} of aggregate ${describeAggregateIdentity(options.identity)}. The original persistence failure is preserved as cause; the mapper failure is available as mapperCause.`, options.persistenceError);
2441
- this.identity = detachAggregateIdentity(options.identity);
2480
+ this.identity = copyAggregateIdentity(options.identity);
2442
2481
  this.intent = options.intent;
2443
2482
  this.mapperCause = options.mapperError;
2444
2483
  }
@@ -2463,7 +2502,7 @@ var InvalidFlushStatementError = class extends KitWiringError {
2463
2502
  classifierCause;
2464
2503
  constructor(options) {
2465
2504
  super("INVALID_FLUSH_STATEMENT", flushStatementReasonMessage(options), options.cause);
2466
- this.identity = detachAggregateIdentity(options.identity);
2505
+ this.identity = copyAggregateIdentity(options.identity);
2467
2506
  this.intent = options.intent ?? null;
2468
2507
  this.reason = options.reason;
2469
2508
  this.received = options.received;
@@ -2510,7 +2549,7 @@ var AggregateTrackingError = class extends KitWiringError {
2510
2549
  registeredIntent;
2511
2550
  constructor(options) {
2512
2551
  super("AGGREGATE_TRACKING", trackingReasonMessage(options));
2513
- this.identity = detachAggregateIdentity(options.identity);
2552
+ this.identity = copyAggregateIdentity(options.identity);
2514
2553
  this.operation = options.operation;
2515
2554
  this.reason = options.reason;
2516
2555
  this.registeredIntent = options.registeredIntent;
@@ -2543,10 +2582,10 @@ function trackingReasonMessage(options) {
2543
2582
  * A `CommitError` is the **potentially transient** post-completion
2544
2583
  * failure (a commit-time serialization failure is the classic case), so
2545
2584
  * it is the one a retrying caller should consider re-running. The
2546
- * deterministic post-completion failure, a harvest-guard violation (an
2585
+ * deterministic post-completion failure, a collection-guard violation (an
2547
2586
  * event missing `aggregateId` / `aggregateType`, or an eventful persisted
2548
2587
  * aggregate that did not advance its version), is a programming bug and surfaces as
2549
- * {@link EventHarvestError} instead, which does NOT extend
2588
+ * {@link EventCollectionError} instead, which does NOT extend
2550
2589
  * `InfrastructureError`, so it stays out of retry paths by construction.
2551
2590
  */
2552
2591
  var CommitError = class extends InfrastructureError {
@@ -2598,29 +2637,6 @@ function wiringErrorInCauseChain(error) {
2598
2637
  return findInCauseChain(error, (link) => isWiringErrorLike(link) ? link : void 0);
2599
2638
  }
2600
2639
 
2601
- //#endregion
2602
- //#region src/domain/aggregate/pending-event-recording.ts
2603
- const { registry: capabilities$1, require } = createGlobalCapabilityRegistry(Symbol.for("@shirudo/ddd-kit/pending-event-recording-registry/v2"));
2604
- /** Resolves the recording capability or throws `UnmanagedInstanceError`. */
2605
- function requirePendingEventRecordingCapability(aggregate, operation) {
2606
- return require(aggregate, operation, "aggregate");
2607
- }
2608
- function registerPendingEventRecordingCapability(aggregate, capability) {
2609
- capabilities$1.set(aggregate, Object.freeze(capability));
2610
- }
2611
-
2612
- //#endregion
2613
- //#region src/application/unit-of-work/record-pending-events.ts
2614
- function recordPendingEvents(aggregate, source, stampOptions) {
2615
- const capability = requirePendingEventRecordingCapability(aggregate, "recordPendingEvents");
2616
- const shared = {
2617
- occurredAt: stampOptions?.occurredAt,
2618
- metadata: stampOptions?.metadata
2619
- };
2620
- const createStamp = typeof source === "function" ? source : () => source.createStamp(shared);
2621
- return capability.record((event, index) => createStamp(event, index));
2622
- }
2623
-
2624
2640
  //#endregion
2625
2641
  //#region src/application/unit-of-work/repository-facade.ts
2626
2642
  /**
@@ -2836,7 +2852,7 @@ function deleteRepositoryFacadeProperty(state, target, property) {
2836
2852
  * `AggregatePersistence` implementations: two `findById(id)` calls in the same
2837
2853
  * unit of work MUST return the same instance, because commit-token
2838
2854
  * write registration dedupes by JavaScript object identity. Two instances for
2839
- * one logical aggregate can otherwise produce two tokens, two harvests,
2855
+ * one logical aggregate can otherwise produce two tokens, two collections,
2840
2856
  * and two post-commit lifecycle calls.
2841
2857
  *
2842
2858
  * Storage is two-level (per-type stores created lazily), so
@@ -2903,7 +2919,7 @@ var IdentityMap = class {
2903
2919
  * - Registering a DIFFERENT instance for an occupied type+id throws:
2904
2920
  * that is precisely the identity-map violation this class exists
2905
2921
  * to prevent (the repository hydrated twice instead of checking
2906
- * {@link get} first), and letting it pass would double-harvest
2922
+ * {@link get} first), and letting it pass would double-collect
2907
2923
  * events downstream.
2908
2924
  * - Registering a type+id that was {@link delete}d in this unit of
2909
2925
  * work throws `AggregateDeletedError`: deletion is final within
@@ -2918,7 +2934,7 @@ var IdentityMap = class {
2918
2934
  this._stores.set(type, store);
2919
2935
  }
2920
2936
  const existing = store.get(id);
2921
- if (existing !== void 0 && existing !== aggregate) throw new Error(`IdentityMap: a different instance is already registered for ${describeAggregateIdentity(aggregate.aggregateIdentity)}. Check get() before hydrating: two live instances of one aggregate break the one-instance-per-unit-of-work contract that exactly-once event harvest relies on.`);
2937
+ if (existing !== void 0 && existing !== aggregate) throw new Error(`IdentityMap: a different instance is already registered for ${describeAggregateIdentity(aggregate.aggregateIdentity)}. Check get() before hydrating: two live instances of one aggregate break the one-instance-per-unit-of-work contract that exactly-once event collection relies on.`);
2922
2938
  store.set(id, aggregate);
2923
2939
  if (aggregate !== null && typeof aggregate === "object" && !this._pendingAtRegistration.has(aggregate)) {
2924
2940
  const pending = pendingEventCountOf(aggregate);
@@ -2929,7 +2945,7 @@ var IdentityMap = class {
2929
2945
  * Registered instances that have recorded MORE pending events than they
2930
2946
  * carried when first registered (loaded). Used by the unit of work's
2931
2947
  * end-of-run guard: an aggregate that gained events after load but was
2932
- * never enrolled would silently drop them. A read-only load, or a
2948
+ * never registered would silently drop them. A read-only load, or a
2933
2949
  * reconstitution that already carried events, shows no increase and is
2934
2950
  * not reported.
2935
2951
  */
@@ -3008,7 +3024,7 @@ function insertPersistenceBaseline(model) {
3008
3024
  return createBaselineToken(model, void 0, "new");
3009
3025
  }
3010
3026
  /**
3011
- * Captures the aggregate's current adapter projection using the capability
3027
+ * Captures the aggregate's current adapter capture using the capability
3012
3028
  * carried by an existing baseline. Used to seal persistence-last registration.
3013
3029
  */
3014
3030
  function recapturePersistenceBaseline(baseline, aggregate) {
@@ -3020,7 +3036,7 @@ function recapturePersistenceBaseline(baseline, aggregate) {
3020
3036
  });
3021
3037
  }
3022
3038
  /**
3023
- * Recaptures the adapter projection and reports whether it drifted from the
3039
+ * Recaptures the adapter capture and reports whether it drifted from the
3024
3040
  * baseline's stored capture, using the model's `captureEquals` when supplied
3025
3041
  * and structural deep equality otherwise.
3026
3042
  *
@@ -3031,8 +3047,8 @@ function recapturePersistenceBaseline(baseline, aggregate) {
3031
3047
  * question independent of the model's diffing strategy. A `"new"` lifecycle
3032
3048
  * baseline has no stored capture and never reports drift.
3033
3049
  */
3034
- function persistenceProjectionDrifted(baseline, aggregate) {
3035
- const capability = capabilityFor(baseline, "persistenceProjectionDrifted");
3050
+ function persistenceCaptureDrifted(baseline, aggregate) {
3051
+ const capability = capabilityFor(baseline, "persistenceCaptureDrifted");
3036
3052
  if (capability.lifecycle === "new") return false;
3037
3053
  return !capability.captureEquals(capability.baseline, capability.capture(aggregate));
3038
3054
  }
@@ -3076,7 +3092,8 @@ function capabilityFor(baseline, operation) {
3076
3092
  * the public API.
3077
3093
  */
3078
3094
  var Session = class {
3079
- commitEnrollment;
3095
+ commitRegistration;
3096
+ stamp;
3080
3097
  _registeredWrites = [];
3081
3098
  _commitTokens = /* @__PURE__ */ new Set();
3082
3099
  _identityMap = new IdentityMap();
@@ -3092,15 +3109,16 @@ var Session = class {
3092
3109
  /**
3093
3110
  * True from the start of the flush until the last check before the
3094
3111
  * commit ran or the run ended. A write registered in that window would
3095
- * be committed and harvested without its flush, so it is rejected, and
3112
+ * be committed and collected without its flush, so it is rejected, and
3096
3113
  * the first rejection also fails the attempt, even when the caller
3097
3114
  * swallows it. After the window, a registration reports a closed unit of
3098
3115
  * work: nothing can fail the attempt any more.
3099
3116
  */
3100
3117
  _lateRegistrationsFailAttempt = false;
3101
3118
  _registrationDuringFlush;
3102
- constructor(commitEnrollment) {
3103
- this.commitEnrollment = commitEnrollment;
3119
+ constructor(commitRegistration, stamp) {
3120
+ this.commitRegistration = commitRegistration;
3121
+ this.stamp = stamp;
3104
3122
  }
3105
3123
  get identityMap() {
3106
3124
  this.assertOpen("tracking.identityMap");
@@ -3195,7 +3213,7 @@ var Session = class {
3195
3213
  throw error;
3196
3214
  }
3197
3215
  }
3198
- /** Two live instances of one identity would each flush and harvest. */
3216
+ /** Two live instances of one identity would each flush and collect. */
3199
3217
  assertIdentityNotTaken(aggregate, definition) {
3200
3218
  const tracked = this._identityMap.get(definition.aggregate, aggregate.id);
3201
3219
  if (tracked === void 0 || tracked === aggregate) return;
@@ -3221,8 +3239,9 @@ var Session = class {
3221
3239
  const loaded = this.loadedEntryFor(aggregate, "remove", definition);
3222
3240
  this.registerWrite(loaded, "remove", definition);
3223
3241
  }
3224
- /** Registers persistence intent and commit enrollment as one operation. */
3242
+ /** Registers persistence intent and commit registration as one operation. */
3225
3243
  registerWrite(entry, intent, definition) {
3244
+ if (entry.registration === void 0) this.stamp(entry.aggregate, `repository.${intent}`);
3226
3245
  const newlyRegistered = this.registerIntent(entry, intent);
3227
3246
  try {
3228
3247
  if (intent === "remove") this.registerRemovedCommit(entry.aggregate, definition, entry.expectedVersion);
@@ -3279,7 +3298,7 @@ var Session = class {
3279
3298
  this._registeredWrites.push(entry);
3280
3299
  return true;
3281
3300
  }
3282
- /** Restores the pre-registration state when commit enrollment rejects. */
3301
+ /** Restores the pre-registration state when commit registration rejects. */
3283
3302
  rollbackIntentRegistration(entry) {
3284
3303
  const index = this._registeredWrites.lastIndexOf(entry);
3285
3304
  if (index >= 0) this._registeredWrites.splice(index, 1);
@@ -3289,7 +3308,7 @@ var Session = class {
3289
3308
  const registration = entry.registration;
3290
3309
  if (registration === void 0) return;
3291
3310
  const currentEvents = entry.aggregate.pendingEvents;
3292
- const persistenceChanged = persistenceProjectionDrifted(registration.baseline, entry.aggregate);
3311
+ const persistenceChanged = persistenceCaptureDrifted(registration.baseline, entry.aggregate);
3293
3312
  const sameEvents = currentEvents.length === registration.events.length && currentEvents.every((event, index) => event === registration.events[index]);
3294
3313
  if (registration.version !== entry.aggregate.version || !sameEvents || persistenceChanged) throw new AggregateTrackingError({
3295
3314
  identity: entry.aggregate.aggregateIdentity,
@@ -3301,13 +3320,13 @@ var Session = class {
3301
3320
  registerSavedCommit(aggregate, definition, expectedVersion) {
3302
3321
  this.assertOpen("repository.add/update");
3303
3322
  if (this.isRemovedInstance(aggregate) || this._identityMap.isDeleted(definition.aggregate, aggregate.id)) throw new AggregateDeletedError({ identity: aggregate.aggregateIdentity });
3304
- const token = this.commitEnrollment.enrollSaved(aggregate, { expectedVersion });
3323
+ const token = this.commitRegistration.registerSaved(aggregate, { expectedVersion });
3305
3324
  this._commitTokens.add(token);
3306
3325
  return token;
3307
3326
  }
3308
3327
  registerRemovedCommit(aggregate, definition, expectedVersion) {
3309
3328
  this.assertOpen("repository.remove");
3310
- const token = this.commitEnrollment.enrollDeleted(aggregate, { expectedVersion });
3329
+ const token = this.commitRegistration.registerRemoved(aggregate, { expectedVersion });
3311
3330
  this._identityMap.delete(definition.aggregate, aggregate.id);
3312
3331
  this._commitTokens.add(token);
3313
3332
  return token;
@@ -3326,7 +3345,7 @@ var Session = class {
3326
3345
  this.assertUnchangedAfterRegistration(entry, "commit");
3327
3346
  continue;
3328
3347
  }
3329
- if (entry.lifecycle === "loaded" && (entry.aggregate.version !== entry.expectedVersion || persistenceProjectionDrifted(entry.baseline, entry.aggregate))) throw new UnenrolledChangesError({
3348
+ if (entry.lifecycle === "loaded" && (entry.aggregate.version !== entry.expectedVersion || persistenceCaptureDrifted(entry.baseline, entry.aggregate))) throw new UnregisteredChangesError({
3330
3349
  identity: entry.aggregate.aggregateIdentity,
3331
3350
  appendOnly: entry.definition.appendOnly === true
3332
3351
  });
@@ -3334,7 +3353,7 @@ var Session = class {
3334
3353
  for (const instance of this._identityMap.instancesWithNewPendingEvents()) {
3335
3354
  if (instance !== null && typeof instance === "object" && this.registrationOf(instance) !== void 0) continue;
3336
3355
  const aggregate = instance;
3337
- throw new UnenrolledChangesError({
3356
+ throw new UnregisteredChangesError({
3338
3357
  identity: aggregate.aggregateIdentity,
3339
3358
  appendOnly: this._trackingByAggregate.get(aggregate)?.definition.appendOnly === true
3340
3359
  });
@@ -3524,7 +3543,7 @@ function defineRepository() {
3524
3543
  const builder = (definition) => {
3525
3544
  const branded = { ...definition };
3526
3545
  assertRepositoryDefinitionMembers(branded);
3527
- stampCooperativeBrand(branded, repositoryDefinitionBrand);
3546
+ applyCooperativeBrand(branded, repositoryDefinitionBrand);
3528
3547
  return Object.freeze(branded);
3529
3548
  };
3530
3549
  return builder;
@@ -3535,7 +3554,7 @@ function defineRepository() {
3535
3554
  * transaction and either persist completely or not at all.
3536
3555
  *
3537
3556
  * Built ON TOP of `withCommit` - the commit orchestration (event
3538
- * harvest into the outbox inside the transaction, internal acknowledgement
3557
+ * collection into the outbox inside the transaction, internal acknowledgement
3539
3558
  * after the commit, best-effort in-process publish last) is inherited,
3540
3559
  * not reimplemented. What this layer adds:
3541
3560
  *
@@ -3603,7 +3622,7 @@ var UnitOfWork = class {
3603
3622
  * Execute one unit of work: open the transaction, hand the callback
3604
3623
  * tx-bound repositories, commit on resolve, roll back on throw,
3605
3624
  * run the post-commit lifecycle (acknowledge, observe, publish) for every
3606
- * enrolled aggregate. Returns the callback's result.
3625
+ * registered aggregate. Returns the callback's result.
3607
3626
  */
3608
3627
  async run(work, options) {
3609
3628
  if (options?.signal?.aborted) throw abortReason(options.signal, "UnitOfWork.run aborted before opening a transaction");
@@ -3621,10 +3640,12 @@ var UnitOfWork = class {
3621
3640
  onPersisted: this.deps.onPersisted,
3622
3641
  onPersistError: this.deps.onPersistError,
3623
3642
  postCommitTimeoutMs: this.deps.postCommitTimeoutMs,
3624
- signal: options?.signal
3625
- }, async (tx, enrollment) => {
3643
+ signal: options?.signal,
3644
+ eventStampFactory: this.deps.eventStampFactory,
3645
+ stampOptions: options?.stampOptions
3646
+ }, async (tx, registration, stamp) => {
3626
3647
  session?.close();
3627
- const s = new Session(enrollment);
3648
+ const s = new Session(registration, stamp);
3628
3649
  session = s;
3629
3650
  sessions.push(s);
3630
3651
  const current = startAttempt();
@@ -3712,7 +3733,7 @@ function startAttempt() {
3712
3733
  * {@link RollbackError}.
3713
3734
  * - `workCompleted`: the callback finished; the failure is post-completion.
3714
3735
  * The checks run in this order. First, a wiring error in the rejection (a
3715
- * harvest-guard violation, or an aggregate that changed after its
3736
+ * collection-guard violation, or an aggregate that changed after its
3716
3737
  * registration) is a deterministic programming bug and passes through; it
3717
3738
  * does NOT extend `InfrastructureError`, so a retry-on-Infrastructure
3718
3739
  * handler skips it. It is thrown inside `scope.transactional()`, so a
@@ -4097,7 +4118,7 @@ var Entity = class {
4097
4118
  * `TState` publicly would expose the aggregate's live object graph and
4098
4119
  * let nested mutation bypass behavior, validation, versioning, and
4099
4120
  * dirty tracking. Concrete entities should expose business-meaningful queries or
4100
- * detached immutable DTOs (`deepFreeze(detachState(this.state))` for a
4121
+ * immutable DTOs (`deepFreeze(copyPlainState(this.state))` for a
4101
4122
  * plain-data state). Snapshot projection belongs to the persistence
4102
4123
  * adapter, which captures the aggregate from outside through those
4103
4124
  * queries and DTOs rather than asking the entity to create its own
@@ -4130,7 +4151,7 @@ var Entity = class {
4130
4151
  * widens to the whole graph: NESTED objects are frozen in place too.
4131
4152
  *
4132
4153
  * @throws MissingEntityIdError when `id` is not a non-blank string.
4133
- * @throws HostileStateKeyError when a plain-object, null-prototype,
4154
+ * @throws PrototypePollutionError when a plain-object, null-prototype,
4134
4155
  * or array state carries an own `"__proto__"` data key; validate and
4135
4156
  * strip untrusted input at the boundary.
4136
4157
  */
@@ -4154,7 +4175,7 @@ var Entity = class {
4154
4175
  * ownership transfer and is frozen in place; see the constructor.
4155
4176
  *
4156
4177
  * @param newState - The new state
4157
- * @throws HostileStateKeyError when the state carries an own
4178
+ * @throws PrototypePollutionError when the state carries an own
4158
4179
  * `"__proto__"` data key; the previous state is kept.
4159
4180
  */
4160
4181
  setState(newState) {
@@ -4215,15 +4236,15 @@ function freezeShallow(value) {
4215
4236
  * @internal Shared by the kit's own entity subclasses; not part of the
4216
4237
  * public API.
4217
4238
  */
4218
- function assertStateHasNoHostileOwnKey(state, subject) {
4239
+ function assertStateHasNoOwnProtoKey(state, subject) {
4219
4240
  if (state === null || typeof state !== "object") return;
4220
4241
  if (Array.isArray(state)) {
4221
- assertNoHostileOwnProtoKey(state, subject);
4242
+ assertNoOwnProtoKey(state, subject);
4222
4243
  return;
4223
4244
  }
4224
4245
  const proto = Object.getPrototypeOf(state);
4225
4246
  if (proto !== Object.prototype && proto !== null) return;
4226
- assertNoHostileOwnProtoKey(state, subject);
4247
+ assertNoOwnProtoKey(state, subject);
4227
4248
  }
4228
4249
  /**
4229
4250
  * Returns a shallow copy for plain objects and arrays so the subsequent
@@ -4235,7 +4256,7 @@ function assertStateHasNoHostileOwnKey(state, subject) {
4235
4256
  */
4236
4257
  function shallowCopyOwned(value) {
4237
4258
  if (value === null || typeof value !== "object") return value;
4238
- assertStateHasNoHostileOwnKey(value, "Entity state");
4259
+ assertStateHasNoOwnProtoKey(value, "Entity state");
4239
4260
  if (Array.isArray(value)) {
4240
4261
  const copy = [...value];
4241
4262
  for (const key of Reflect.ownKeys(value)) {
@@ -4344,7 +4365,7 @@ function removeEntityById(entities, id) {
4344
4365
  /**
4345
4366
  * Updates an entity with the given ID in the collection.
4346
4367
  * Returns a new array with the updated entity.
4347
- * Structural sharing for adapter-owned persistence projections: returns
4368
+ * Structural sharing for adapter-owned persistence captures: returns
4348
4369
  * the ORIGINAL array when nothing changed (no match, or the element kept
4349
4370
  * its reference), so a partial-write adapter can skip the untouched
4350
4371
  * collection; a new array only when an
@@ -4383,7 +4404,7 @@ function updateEntityById(entities, id, updater) {
4383
4404
  /**
4384
4405
  * Replaces an entity with the given ID in the collection.
4385
4406
  * Returns a new array with the replaced entity.
4386
- * Structural sharing for adapter-owned persistence projections: returns
4407
+ * Structural sharing for adapter-owned persistence captures: returns
4387
4408
  * the ORIGINAL array when nothing changed (no match, or the element kept
4388
4409
  * its reference), so a partial-write adapter can skip the untouched
4389
4410
  * collection; a new array only when an
@@ -4448,8 +4469,8 @@ function entityIds(entities) {
4448
4469
  * tracking, the kit-internal post-commit acknowledgement capability,
4449
4470
  * the `markReconstituted` post-load marker, and the `createEvent` helper
4450
4471
  * that auto-injects `aggregateId` + `aggregateType` on every event the
4451
- * aggregate emits. The application shell records the pending decisions
4452
- * with `recordPendingEvents` before persistence.
4472
+ * aggregate emits. The commit stamps the pending decisions when it
4473
+ * registers the aggregate, before persistence.
4453
4474
  *
4454
4475
  * Consumers do NOT extend this class directly; extend
4455
4476
  * `StateStoredAggregate` for state-stored aggregates or
@@ -4509,24 +4530,24 @@ var BaseAggregate = class extends Entity {
4509
4530
  registerPendingEventRecordingCapability(this, { record: (createStamp) => this.recordPendingDecisions(createStamp) });
4510
4531
  }
4511
4532
  /**
4512
- * Stamps every uncommitted decision in the pending list with identity
4513
- * and time from `createStamp`, passes an already recorded event through,
4533
+ * Stamps every unstamped decision in the pending list with identity
4534
+ * and time from `createStamp`, passes an already stamped event through,
4514
4535
  * and replaces the list atomically. Three guards run before the
4515
- * assignment: an event that no kit constructor minted is rejected
4516
- * ({@link UnmintedEventError}); a stamp provider that recorded a new
4536
+ * assignment: an event that no kit constructor built is rejected
4537
+ * ({@link HandBuiltEventError}); a stamp provider that recorded a new
4517
4538
  * decision on this aggregate mid-map is rejected
4518
4539
  * ({@link ReentrantEventRecordingError}); and two decisions that
4519
4540
  * received one eventId are rejected ({@link DuplicateEventIdError}).
4520
- * When a guard fires, every decision stays unrecorded.
4541
+ * When a guard fires, every decision stays unstamped.
4521
4542
  */
4522
4543
  recordPendingDecisions(createStamp) {
4523
4544
  const stamped = this._pendingEvents;
4524
4545
  const stampedCount = stamped.length;
4525
4546
  const recorded = stamped.map((event, index) => {
4526
4547
  const candidate = event;
4527
- if (isRecordedDomainEvent(candidate)) return candidate;
4528
- if (!isUncommittedDomainEvent(candidate)) throw new UnmintedEventError(event.type);
4529
- return recordDomainEvent(candidate, createStamp(candidate, index));
4548
+ if (isStampedDomainEvent(candidate)) return candidate;
4549
+ if (!isUnstampedDomainEvent(candidate)) throw new HandBuiltEventError(event.type);
4550
+ return stampDomainEvent(candidate, createStamp(candidate, index));
4530
4551
  });
4531
4552
  if (this._pendingEvents !== stamped || this._pendingEvents.length !== stampedCount) throw new ReentrantEventRecordingError({ identity: this.aggregateIdentity });
4532
4553
  const seenEventIds = /* @__PURE__ */ new Set();
@@ -4549,7 +4570,7 @@ var BaseAggregate = class extends Entity {
4549
4570
  /**
4550
4571
  * Post-commit cleanup for the deleted disposition. The row is gone, so
4551
4572
  * there is no persisted version to advance: stamping the marker from the
4552
- * live instance would make a later legitimate re-enrollment of this
4573
+ * live instance would make a later legitimate re-registration of this
4553
4574
  * instance trip the unique-cursor guard for a row that does not exist.
4554
4575
  */
4555
4576
  discardPendingEventsAfterDeletion(events) {
@@ -4636,12 +4657,12 @@ var BaseAggregate = class extends Entity {
4636
4657
  this._persistedVersion = restored;
4637
4658
  }
4638
4659
  /**
4639
- * Appends a domain event to the pending list. The event must be minted
4660
+ * Appends a domain event to the pending list. The event must be kit-built
4640
4661
  * by a kit constructor; a missing `aggregateId` or `aggregateType` is
4641
4662
  * stamped from this aggregate, and an aggregate identity that names another
4642
4663
  * aggregate throws {@link MisattributedEventError} before anything is
4643
4664
  * recorded. Each append is one fact. A decision appended twice becomes
4644
- * two facts with distinct ids. A recorded event that is already pending
4665
+ * two facts with distinct ids. A stamped event that is already pending
4645
4666
  * throws {@link DuplicateEventIdError}, and a list at `maxPendingEvents`
4646
4667
  * throws {@link PendingEventLimitExceededError}, before anything is
4647
4668
  * recorded. Prefer the higher-level `StateStoredAggregate.setState()`
@@ -4654,23 +4675,23 @@ var BaseAggregate = class extends Entity {
4654
4675
  * application shell rejects it; use `setState(currentState, event)`.
4655
4676
  */
4656
4677
  addDomainEvent(event) {
4657
- const stamped = this.stampNewEventIdentity(event);
4678
+ const stamped = this.attributeNewEvent(event);
4658
4679
  this.assertEventIdsNotPending([stamped]);
4659
4680
  this.assertPendingEventLimit(1);
4660
4681
  this.appendStampedEvent(stamped);
4661
4682
  }
4662
4683
  /**
4663
4684
  * One identity per fact, checked before the change becomes observable:
4664
- * a recorded event whose `eventId` is already pending, or appears twice
4685
+ * a stamped event whose `eventId` is already pending, or appears twice
4665
4686
  * in one batch, throws {@link DuplicateEventIdError}. A decision has no
4666
4687
  * `eventId` yet, so a stamp provider that reuses one is caught at
4667
4688
  * recording instead.
4668
4689
  */
4669
4690
  assertEventIdsNotPending(batch) {
4670
4691
  const pendingIds = /* @__PURE__ */ new Set();
4671
- for (const pending of this._pendingEvents) if (isRecordedDomainEvent(pending)) pendingIds.add(pending.eventId);
4692
+ for (const pending of this._pendingEvents) if (isStampedDomainEvent(pending)) pendingIds.add(pending.eventId);
4672
4693
  for (const event of batch) {
4673
- if (!isRecordedDomainEvent(event)) continue;
4694
+ if (!isStampedDomainEvent(event)) continue;
4674
4695
  if (pendingIds.has(event.eventId)) throw new DuplicateEventIdError({
4675
4696
  identity: this.aggregateIdentity,
4676
4697
  eventId: event.eventId
@@ -4698,7 +4719,7 @@ var BaseAggregate = class extends Entity {
4698
4719
  }
4699
4720
  /**
4700
4721
  * Appends an event that the caller already passed through
4701
- * {@link stampNewEventIdentity}, {@link assertEventIdsNotPending} and
4722
+ * {@link attributeNewEvent}, {@link assertEventIdsNotPending} and
4702
4723
  * {@link assertPendingEventLimit}. `setState()` and `apply()` run the
4703
4724
  * three before the state moves and append afterwards, so each gate
4704
4725
  * runs once per event.
@@ -4719,15 +4740,15 @@ var BaseAggregate = class extends Entity {
4719
4740
  * present-but-foreign `aggregateId` / `aggregateType` is a wiring bug and
4720
4741
  * throws {@link MisattributedEventError}; missing fields are filled in
4721
4742
  * from the aggregate, so a recorded event always carries its full
4722
- * aggregate identity and can never fail the harvest or the replay guard
4723
- * later. The mint gate
4724
- * runs first, so an unminted event fails before anything else. The
4743
+ * aggregate identity and can never fail the collection or the replay guard
4744
+ * later. The kit-built check
4745
+ * runs first, so a hand-built event fails before anything else. The
4725
4746
  * stamped copy is frozen like the original (payload and metadata are
4726
4747
  * shared, already deep-frozen by the constructors); a fully stamped
4727
4748
  * event is returned as is.
4728
4749
  */
4729
- stampNewEventIdentity(event) {
4730
- this.assertMintedEvent(event);
4750
+ attributeNewEvent(event) {
4751
+ this.assertKitBuiltEvent(event);
4731
4752
  const { aggregateId, aggregateType } = event;
4732
4753
  const idForeign = aggregateId !== void 0 && aggregateId !== this.id;
4733
4754
  const typeForeign = aggregateType !== void 0 && aggregateType !== this.aggregateType;
@@ -4748,32 +4769,31 @@ var BaseAggregate = class extends Entity {
4748
4769
  aggregateId: this.id,
4749
4770
  aggregateType: this.aggregateType
4750
4771
  };
4751
- return isRecordedDomainEvent(event) ? adoptRecordedDomainEvent(copy) : adoptUncommittedDomainEvent(copy);
4772
+ return isStampedDomainEvent(event) ? adoptStampedDomainEvent(copy) : adoptUnstampedDomainEvent(copy);
4752
4773
  }
4753
4774
  /**
4754
- * Mint gate for every recording path: only an event that a kit
4755
- * constructor produced passes. The kit marks two shapes: a recorded
4756
- * event carries the recorded brand (`createDomainEvent`,
4757
- * `createDomainEventFromFacts`) and a decision carries the uncommitted
4758
- * brand (`createUncommittedDomainEvent`, the aggregate `createEvent`
4759
- * helper). Either brand implies deeply frozen with defensively copied
4775
+ * Kit-built check for every recording path: only an event that a kit
4776
+ * constructor produced passes. The kit marks two shapes: a stamped
4777
+ * event carries the stamped brand (`createDomainEvent`,
4778
+ * `createStampedDomainEvent`) and a decision carries the unstamped
4779
+ * brand (the aggregate `createEvent` helper). Either brand implies deeply frozen with defensively copied
4760
4780
  * payload and metadata, a guarantee no frozen-ness probe can establish
4761
4781
  * (a shallow-frozen literal with mutable nested data would fool it).
4762
4782
  * O(1): one brand check per shape.
4763
4783
  */
4764
- assertMintedEvent(event) {
4765
- if (!isRecordedDomainEvent(event) && !isUncommittedDomainEvent(event)) throw new UnmintedEventError(event.type);
4784
+ assertKitBuiltEvent(event) {
4785
+ if (!isStampedDomainEvent(event) && !isUnstampedDomainEvent(event)) throw new HandBuiltEventError(event.type);
4766
4786
  }
4767
4787
  /**
4768
4788
  * Creates the immutable business fact accepted by this aggregate without
4769
4789
  * reading a clock, generating an id, or attaching tracing metadata.
4770
4790
  *
4771
- * The application shell records pending events after the domain operation
4772
- * and before persistence. Payload schema version stays here, next to the
4773
- * concrete event producer, rather than in shell-owned recording data.
4791
+ * The commit stamps pending events when it registers the aggregate,
4792
+ * before persistence. Payload schema version stays here, next to the
4793
+ * concrete event producer, rather than in shell-owned stamp data.
4774
4794
  */
4775
4795
  createEvent(type, payload, options) {
4776
- return mintUncommittedDomainEvent(type, payload, {
4796
+ return buildUnstampedDomainEvent(type, payload, {
4777
4797
  ...options,
4778
4798
  aggregateId: this.id,
4779
4799
  aggregateType: this.aggregateType
@@ -4785,7 +4805,7 @@ var BaseAggregate = class extends Entity {
4785
4805
  * `EventSourcedAggregate.replayHistory`: a target carrying unflushed
4786
4806
  * `pendingEvents` throws {@link UnreplayableAggregateError} BEFORE anything
4787
4807
  * moves. A restore advances the aggregate's current version, so unflushed
4788
- * events recorded against the old version would later be harvested claiming
4808
+ * events recorded against the old version would later be collected claiming
4789
4809
  * a version baseline they were never part of. When the discard is
4790
4810
  * deliberate, discard this dirty instance and reconstitute a fresh aggregate
4791
4811
  * instead of mutating persistence lifecycle state publicly.
@@ -4793,8 +4813,8 @@ var BaseAggregate = class extends Entity {
4793
4813
  * Deliberately a module-level function, not a class method: it MUST not be
4794
4814
  * overridable by consumer subclasses (a no-op override would silently
4795
4815
  * disable the guard at every call site). The callers pass the count of
4796
- * the private list; the commit harvest reads the public `pendingEvents`
4797
- * getter, so a subclass that overrides the getter changes the harvest,
4816
+ * the private list; the commit collection reads the public `pendingEvents`
4817
+ * getter, so a subclass that overrides the getter changes the collection,
4798
4818
  * not this guard.
4799
4819
  *
4800
4820
  * @internal Shared by the aggregate flavours in this package; not part of
@@ -4926,12 +4946,12 @@ var EventSourcedAggregate = class extends BaseAggregate {
4926
4946
  * @param event - The domain event to apply
4927
4947
  */
4928
4948
  apply(event) {
4929
- const stamped = this.stampNewEventIdentity(event);
4949
+ const stamped = this.attributeNewEvent(event);
4930
4950
  this.assertEventIdsNotPending([stamped]);
4931
4951
  this.assertPendingEventLimit(1);
4932
4952
  this.validateEvent(stamped);
4933
4953
  const next = freezeEntityState(this, this.fold(stamped));
4934
- assertStateHasNoHostileOwnKey(next, "Aggregate state");
4954
+ assertStateHasNoOwnProtoKey(next, "Aggregate state");
4935
4955
  assertStateInvariant(this, next);
4936
4956
  this.bumpVersion();
4937
4957
  storeTrustedState(this, next);
@@ -4959,7 +4979,7 @@ var EventSourcedAggregate = class extends BaseAggregate {
4959
4979
  * stream corruption) after the all-or-nothing rollback. History
4960
4980
  * events without the optional aggregate identity fields pass unchecked (the
4961
4981
  * fields are optional on the event shape); NEW events are covered
4962
- * by the stricter `stampNewEventIdentity` on the apply path.
4982
+ * by the stricter `attributeNewEvent` on the apply path.
4963
4983
  */
4964
4984
  assertReplayedEventBelongsHere(event) {
4965
4985
  const idMismatch = event.aggregateId !== void 0 && event.aggregateId !== this.id;
@@ -5020,7 +5040,7 @@ var EventSourcedAggregate = class extends BaseAggregate {
5020
5040
  this.assertReplayedEventBelongsHere(event);
5021
5041
  storeTrustedState(this, this.fold(event));
5022
5042
  }
5023
- assertStateHasNoHostileOwnKey(this.state, "Aggregate state");
5043
+ assertStateHasNoOwnProtoKey(this.state, "Aggregate state");
5024
5044
  this.markReconstituted(startVersion + history.length);
5025
5045
  } catch (e) {
5026
5046
  storeTrustedState(this, previousState);
@@ -5057,20 +5077,20 @@ function reconstituteAggregateFromHistory(createReplayTarget, history) {
5057
5077
  * The aggregate owns identity, valid domain state, behavior, its current
5058
5078
  * domain version, and pending domain events. It deliberately does not own a
5059
5079
  * database baseline or dirty-key bookkeeping. A repository adapter defines
5060
- * its own persistence projection; the application shell retains that opaque
5080
+ * its own persistence capture; the application shell retains that opaque
5061
5081
  * baseline and derives the adapter's change set at flush.
5062
5082
  */
5063
5083
  var StateStoredAggregate = class extends BaseAggregate {
5064
5084
  /**
5065
5085
  * Replaces the state, advances the OCC version, and records the events
5066
- * of the change, in that order. State validation, the event mint gate,
5086
+ * of the change, in that order. State validation, the kit-built check,
5067
5087
  * the aggregate identity check of the event, the pending event-id check,
5068
5088
  * and the pending event limit check run before the change becomes
5069
5089
  * observable, so a rejected decision records nothing and moves nothing.
5070
5090
  * Without events the call is a plain versioned state change.
5071
5091
  */
5072
5092
  setState(newState, events = []) {
5073
- const stamped = (Array.isArray(events) ? events : [events]).map((event) => this.stampNewEventIdentity(event));
5093
+ const stamped = (Array.isArray(events) ? events : [events]).map((event) => this.attributeNewEvent(event));
5074
5094
  this.assertEventIdsNotPending(stamped);
5075
5095
  this.assertPendingEventLimit(stamped.length);
5076
5096
  const next = this.nextVersion();
@@ -5229,684 +5249,16 @@ var NotSpecification = class extends Specification {
5229
5249
  };
5230
5250
 
5231
5251
  //#endregion
5232
- //#region src/domain/state-machine/errors.ts
5233
- /** No transition is defined for the input in the current state. */
5234
- var InvalidDomainTransitionError = class extends DomainError {
5235
- state;
5236
- inputType;
5237
- constructor(state, inputType) {
5238
- super({
5239
- code: "INVALID_DOMAIN_TRANSITION",
5240
- message: `No domain transition from "${state}" on "${inputType}".`
5241
- });
5242
- this.state = state;
5243
- this.inputType = inputType;
5244
- }
5245
- };
5246
- /** A defined transition was rejected by its domain guard. */
5247
- var DomainTransitionGuardRejectedError = class extends DomainError {
5248
- state;
5249
- inputType;
5250
- constructor(state, inputType) {
5251
- super({
5252
- code: "DOMAIN_TRANSITION_GUARD_REJECTED",
5253
- message: `Domain transition guard rejected "${inputType}" from "${state}".`
5254
- });
5255
- this.state = state;
5256
- this.inputType = inputType;
5257
- }
5258
- };
5259
- /** The machine definition violates its runtime contract. */
5260
- var InvalidDomainMachineDefinitionError = class extends KitWiringError {
5261
- constructor(message, cause) {
5262
- super("INVALID_DOMAIN_MACHINE_DEFINITION", message, cause);
5263
- }
5264
- };
5265
- /** Context contains unsupported or unsafe runtime data. */
5266
- var InvalidDomainMachineContextError = class extends KitWiringError {
5267
- constructor(message, cause) {
5268
- super("INVALID_DOMAIN_MACHINE_CONTEXT", message, cause);
5269
- }
5270
- };
5271
- /** A supplied or produced snapshot is malformed or violates invariants. */
5272
- var InvalidDomainMachineSnapshotError = class extends KitWiringError {
5273
- constructor(message, cause) {
5274
- super("INVALID_DOMAIN_MACHINE_SNAPSHOT", message, cause);
5275
- }
5276
- };
5277
- /** An input is malformed or contains unsupported runtime data. */
5278
- var InvalidDomainMachineInputError = class extends KitWiringError {
5279
- constructor(message, cause) {
5280
- super("INVALID_DOMAIN_MACHINE_INPUT", message, cause);
5281
- }
5282
- };
5283
- /** A guard returned a value other than `boolean` or `DomainError`. */
5284
- var InvalidDomainTransitionGuardResultError = class extends KitWiringError {
5285
- constructor(message, cause) {
5286
- super("INVALID_DOMAIN_TRANSITION_GUARD_RESULT", message, cause);
5287
- }
5288
- };
5289
- /** A reducer returned a malformed result or unsupported output data. */
5290
- var InvalidDomainTransitionResultError = class extends KitWiringError {
5291
- constructor(message, cause) {
5292
- super("INVALID_DOMAIN_TRANSITION_RESULT", message, cause);
5293
- }
5294
- };
5295
- /** A callback attempted to evaluate the same stateful machine recursively. */
5296
- var ReentrantDomainStateMachineEvaluationError = class extends KitWiringError {
5297
- constructor() {
5298
- super("REENTRANT_DOMAIN_STATE_MACHINE_EVALUATION", "Domain state machine callbacks cannot evaluate the same machine.");
5299
- }
5300
- };
5301
-
5302
- //#endregion
5303
- //#region src/domain/state-machine/machine-data.ts
5304
- const DOMAIN_MACHINE_DATA_MAX_DEPTH = 256;
5305
- const DOMAIN_MACHINE_DATA_MAX_NODES = 1e4;
5306
- const DOMAIN_MACHINE_DATA_MAX_PROPERTIES = 1e5;
5307
- function copyDomainMachineOutputs(outputs) {
5308
- try {
5309
- const copiedOutputs = cloneDomainMachineDataValue(outputs ?? [], createDomainTransitionOutputError);
5310
- return deepFreeze(copiedOutputs);
5311
- } catch (cause) {
5312
- if (cause instanceof InvalidDomainTransitionResultError) throw cause;
5313
- throw new InvalidDomainTransitionResultError("Domain transition result outputs must contain cloneable, deeply immutable data.", cause);
5314
- }
5315
- }
5316
- function copyDomainMachineInput(input) {
5317
- try {
5318
- return deepFreeze(cloneDomainMachineDataValue(input, createDomainMachineInputError));
5319
- } catch (cause) {
5320
- if (cause instanceof InvalidDomainMachineInputError) throw cause;
5321
- throw new InvalidDomainMachineInputError("Domain machine input must contain cloneable, deeply immutable data.", cause);
5322
- }
5323
- }
5324
- function copyDomainMachineContext(context) {
5325
- try {
5326
- return deepFreeze(cloneDomainMachineDataValue(context, createDomainMachineContextError));
5327
- } catch (cause) {
5328
- if (cause instanceof InvalidDomainMachineContextError) throw cause;
5329
- throw new InvalidDomainMachineContextError("Domain machine context must contain cloneable, deeply immutable data.", cause);
5330
- }
5331
- }
5332
- function createDomainMachineContextError(message, cause) {
5333
- return new InvalidDomainMachineContextError(message, cause);
5334
- }
5335
- function createDomainMachineInputError(message, cause) {
5336
- return new InvalidDomainMachineInputError(message, cause);
5337
- }
5338
- function createDomainTransitionOutputError(message, cause) {
5339
- return new InvalidDomainTransitionResultError(message, cause);
5340
- }
5341
- function cloneDomainMachineDataValue(value, errorFactory, seen = /* @__PURE__ */ new WeakMap(), traversal = {
5342
- nodes: 0,
5343
- properties: 0
5344
- }, depth = 0) {
5345
- if (typeof value === "function") throw errorFactory("Domain machine data cannot contain function values.");
5346
- if (value === null || typeof value !== "object") return value;
5347
- const source = value;
5348
- if (depth > DOMAIN_MACHINE_DATA_MAX_DEPTH) throw errorFactory(`Domain machine data exceeds the maximum depth of ${DOMAIN_MACHINE_DATA_MAX_DEPTH}.`);
5349
- const existing = seen.get(source);
5350
- if (existing !== void 0) return existing;
5351
- traversal.nodes += 1;
5352
- if (traversal.nodes > DOMAIN_MACHINE_DATA_MAX_NODES) throw errorFactory(`Domain machine data contains more than ${DOMAIN_MACHINE_DATA_MAX_NODES.toLocaleString("en-US")} object nodes.`);
5353
- const toStringTagDescriptor = findPropertyDescriptor(source, Symbol.toStringTag);
5354
- if (toStringTagDescriptor !== void 0 && !("value" in toStringTagDescriptor)) throw errorFactory("Domain machine data cannot contain accessor properties.");
5355
- if (Array.isArray(value)) {
5356
- if (!isIntrinsicArrayPrototype(Object.getPrototypeOf(value))) throw errorFactory("Domain machine data cannot contain custom Array instances.");
5357
- const cloned = new Array(value.length);
5358
- seen.set(source, cloned);
5359
- for (const key of readDomainMachineDataKeys(source, errorFactory, traversal)) {
5360
- const descriptor = Object.getOwnPropertyDescriptor(source, key);
5361
- if (!descriptor) continue;
5362
- if (!("value" in descriptor)) throw errorFactory("Domain machine data cannot contain accessor properties.");
5363
- if (key === "length") continue;
5364
- descriptor.value = cloneDomainMachineDataValue(descriptor.value, errorFactory, seen, traversal, depth + 1);
5365
- Object.defineProperty(cloned, key, descriptor);
5366
- }
5367
- return cloned;
5368
- }
5369
- const prototype = Object.getPrototypeOf(source);
5370
- if (prototype !== null && !isIntrinsicObjectPrototype(prototype)) throw errorFactory("Domain machine data cannot contain custom class instances.");
5371
- const tag = Object.prototype.toString.call(source);
5372
- if (isBuiltInObject(source, tag) || ArrayBuffer.isView(source)) throw errorFactory(`Domain machine data cannot contain ${tag.slice(8, -1)} object values.`);
5373
- const cloned = Object.create(prototype === null ? null : Object.prototype);
5374
- seen.set(source, cloned);
5375
- for (const key of readDomainMachineDataKeys(source, errorFactory, traversal)) {
5376
- const descriptor = Object.getOwnPropertyDescriptor(source, key);
5377
- if (!descriptor) continue;
5378
- if (!("value" in descriptor)) throw errorFactory("Domain machine data cannot contain accessor properties.");
5379
- descriptor.value = cloneDomainMachineDataValue(descriptor.value, errorFactory, seen, traversal, depth + 1);
5380
- Object.defineProperty(cloned, key, descriptor);
5381
- }
5382
- return cloned;
5383
- }
5384
- function readDomainMachineDataKeys(value, errorFactory, traversal) {
5385
- const keys = Reflect.ownKeys(value);
5386
- traversal.properties += keys.length;
5387
- if (traversal.properties > DOMAIN_MACHINE_DATA_MAX_PROPERTIES) throw errorFactory(`Domain machine data contains more than ${DOMAIN_MACHINE_DATA_MAX_PROPERTIES.toLocaleString("en-US")} own properties.`);
5388
- return keys;
5389
- }
5390
- function isRecord(value) {
5391
- return typeof value === "object" && value !== null && !Array.isArray(value);
5392
- }
5393
- function isPlainRecord(value) {
5394
- if (!isRecord(value)) return false;
5395
- const prototype = Object.getPrototypeOf(value);
5396
- return prototype === null || isIntrinsicObjectPrototype(prototype);
5397
- }
5398
- function isIntrinsicArrayPrototype(prototype) {
5399
- if (prototype === null || !Array.isArray(prototype)) return false;
5400
- if (!isIntrinsicConstructorPrototype(prototype, "Array")) return false;
5401
- const parentPrototype = Object.getPrototypeOf(prototype);
5402
- return parentPrototype !== null && isIntrinsicObjectPrototype(parentPrototype);
5403
- }
5404
- function isIntrinsicObjectPrototype(prototype) {
5405
- return Object.getPrototypeOf(prototype) === null && isIntrinsicConstructorPrototype(prototype, "Object");
5406
- }
5407
- function hasOwn(value, key) {
5408
- return Object.hasOwn(value, key);
5409
- }
5410
-
5411
- //#endregion
5412
- //#region src/domain/state-machine/definition.ts
5413
- const DOMAIN_MACHINE_DEFINITION_KEYS = /* @__PURE__ */ new Set([
5414
- "initial",
5415
- "initialContext",
5416
- "validateSnapshot",
5417
- "states"
5418
- ]);
5419
- const DOMAIN_MACHINE_STATE_NODE_KEYS = /* @__PURE__ */ new Set([
5420
- "terminal",
5421
- "validateContext",
5422
- "on"
5423
- ]);
5424
- const DOMAIN_MACHINE_TRANSITION_KEYS = /* @__PURE__ */ new Set([
5425
- "target",
5426
- "guard",
5427
- "reduce"
5428
- ]);
5429
- function copyDomainMachineDefinition(definition) {
5430
- const states = readDomainMachineDefinitionProperty(definition, "states");
5431
- const copiedStates = Object.create(null);
5432
- for (const state of Object.keys(states)) {
5433
- const node = readDomainMachineDefinitionProperty(states, state);
5434
- const transitions = readOptionalDomainMachineDefinitionProperty(node, "on");
5435
- const copiedTransitions = Object.create(null);
5436
- for (const inputType of Object.keys(transitions ?? {})) {
5437
- const transition = readDomainMachineDefinitionProperty(transitions, inputType);
5438
- if (transition) {
5439
- const copiedTransition = Object.freeze({
5440
- target: readDomainMachineDefinitionProperty(transition, "target"),
5441
- guard: readOptionalDomainMachineDefinitionProperty(transition, "guard"),
5442
- reduce: readOptionalDomainMachineDefinitionProperty(transition, "reduce")
5443
- });
5444
- Object.defineProperty(copiedTransitions, inputType, {
5445
- value: copiedTransition,
5446
- enumerable: true
5447
- });
5448
- }
5449
- }
5450
- Object.defineProperty(copiedStates, state, {
5451
- value: Object.freeze({
5452
- terminal: readOptionalDomainMachineDefinitionProperty(node, "terminal"),
5453
- validateContext: readOptionalDomainMachineDefinitionProperty(node, "validateContext"),
5454
- on: Object.freeze(copiedTransitions)
5455
- }),
5456
- enumerable: true
5457
- });
5458
- }
5459
- return Object.freeze({
5460
- initial: readDomainMachineDefinitionProperty(definition, "initial"),
5461
- initialContext: readDomainMachineDefinitionProperty(definition, "initialContext"),
5462
- validateSnapshot: readOptionalDomainMachineDefinitionProperty(definition, "validateSnapshot"),
5463
- states: Object.freeze(copiedStates)
5464
- });
5465
- }
5466
- /**
5467
- * Registry of definitions produced by `prepareDomainMachineDefinition`:
5468
- * validated, defensively copied, deeply frozen. The runtime membership
5469
- * proof lives in this module-private WeakSet (the stable copies are
5470
- * frozen and must stay pure data, so no runtime brand property); the
5471
- * compile-time proof is the required type brand on
5472
- * {@link PreparedDomainMachineDefinition}. Only
5473
- * `prepareDomainMachineDefinition` below adds to the set, so membership
5474
- * always implies validated + copied + frozen.
5475
- */
5476
- const preparedDefinitions = /* @__PURE__ */ new WeakSet();
5477
- /**
5478
- * The entry-point normalization every pure function and the
5479
- * `DomainStateMachine` constructor share: a prepared definition passes
5480
- * through untouched (already validated, copied, frozen); anything else
5481
- * pays the documented per-call validate-and-copy.
5482
- */
5483
- function ensureStableDomainMachineDefinition(definition) {
5484
- if (preparedDefinitions.has(definition)) return definition;
5485
- validateDomainMachineDefinition(definition);
5486
- const stable = copyDomainMachineDefinition(definition);
5487
- validateDomainMachineDefinition(stable);
5488
- return stable;
5489
- }
5490
- /**
5491
- * Validates and stabilizes a machine definition ONCE, for repeated use
5492
- * with the pure functions. Without it, `transitionDomainState` and
5493
- * `canTransitionDomainState` re-validate and defensively re-copy the
5494
- * WHOLE definition on every call (the documented safety of the raw
5495
- * path); on a hot dispatch path that is avoidable O(definition) work.
5496
- * The `DomainStateMachine` class does the equivalent once in its
5497
- * constructor; this export brings the same amortization to pure-API
5498
- * users:
5499
- *
5500
- * ```ts
5501
- * const prepared = prepareDomainMachineDefinition(orderLifecycle);
5502
- * // per dispatch: no re-validation, no definition copy
5503
- * const outcome = transitionDomainState(prepared, snapshot, input);
5504
- * ```
5505
- *
5506
- * The returned definition is a deeply frozen copy, isolated from later
5507
- * mutation of the input object. Preparing an already-prepared
5508
- * definition returns it unchanged.
5509
- */
5510
- function prepareDomainMachineDefinition(definition) {
5511
- const stable = ensureStableDomainMachineDefinition(definition);
5512
- preparedDefinitions.add(stable);
5513
- return stable;
5514
- }
5515
- function getTransition(definition, state, input) {
5516
- const transitions = definition.states[state].on;
5517
- if (!transitions || !hasOwn(transitions, input.type)) return void 0;
5518
- return transitions[input.type];
5519
- }
5520
- function validateDomainMachineDefinition(definition) {
5521
- const candidate = definition;
5522
- if (!isPlainRecord(candidate)) throw new InvalidDomainMachineDefinitionError("Domain machine definition must be a plain object.");
5523
- assertDomainMachineDefinitionDataProperties(candidate, DOMAIN_MACHINE_DEFINITION_KEYS);
5524
- const initial = readDomainMachineDefinitionProperty(candidate, "initial");
5525
- if (typeof initial !== "string") throw new InvalidDomainMachineDefinitionError("Domain machine initial state must be a string data property.");
5526
- if (typeof readDomainMachineDefinitionProperty(candidate, "initialContext") !== "function") throw new InvalidDomainMachineDefinitionError("Domain machine initialContext must be a function data property.");
5527
- const validateSnapshot = readOptionalDomainMachineDefinitionProperty(candidate, "validateSnapshot");
5528
- if (validateSnapshot !== void 0 && typeof validateSnapshot !== "function") throw new InvalidDomainMachineDefinitionError("Domain machine validateSnapshot must be a function data property.");
5529
- const statesCandidate = readDomainMachineDefinitionProperty(candidate, "states");
5530
- if (!isPlainRecord(statesCandidate)) throw new InvalidDomainMachineDefinitionError("Domain machine states must be a plain object data property.");
5531
- const states = statesCandidate;
5532
- assertDomainMachineDefinitionEntryMap(states, "state");
5533
- if (!hasOwn(states, initial)) throw new InvalidDomainMachineDefinitionError(`Initial domain machine state "${initial}" is not defined.`);
5534
- for (const state of Object.keys(states)) {
5535
- const node = readDomainMachineDefinitionProperty(states, state);
5536
- if (!isPlainRecord(node)) throw new InvalidDomainMachineDefinitionError(`Domain machine state "${state}" must be a plain object data property.`);
5537
- assertDomainMachineDefinitionDataProperties(node, DOMAIN_MACHINE_STATE_NODE_KEYS);
5538
- const terminal = readOptionalDomainMachineDefinitionProperty(node, "terminal");
5539
- if (terminal !== void 0 && typeof terminal !== "boolean") throw new InvalidDomainMachineDefinitionError(`Domain machine state "${state}" terminal flag must be a boolean.`);
5540
- const validateContext = readOptionalDomainMachineDefinitionProperty(node, "validateContext");
5541
- if (validateContext !== void 0 && typeof validateContext !== "function") throw new InvalidDomainMachineDefinitionError(`Domain machine state "${state}" validateContext must be a function.`);
5542
- const transitions = readOptionalDomainMachineDefinitionProperty(node, "on");
5543
- if (transitions !== void 0 && !isPlainRecord(transitions)) throw new InvalidDomainMachineDefinitionError(`Domain machine state "${state}" transitions must be a plain object.`);
5544
- if (isPlainRecord(transitions)) assertDomainMachineDefinitionEntryMap(transitions, "input");
5545
- const inputTypes = Object.keys(transitions ?? {});
5546
- if (terminal === true && inputTypes.length > 0) throw new InvalidDomainMachineDefinitionError(`Terminal domain machine state "${state}" cannot declare transitions.`);
5547
- for (const inputType of inputTypes) {
5548
- const transition = readDomainMachineDefinitionProperty(transitions, inputType);
5549
- if (!isPlainRecord(transition)) throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" must be a plain object.`);
5550
- assertDomainMachineDefinitionDataProperties(transition, DOMAIN_MACHINE_TRANSITION_KEYS);
5551
- const target = readDomainMachineDefinitionProperty(transition, "target");
5552
- if (typeof target !== "string") throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" must target a string state.`);
5553
- if (!hasOwn(states, target)) throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" targets unknown state "${target}".`);
5554
- const guard = readOptionalDomainMachineDefinitionProperty(transition, "guard");
5555
- if (guard !== void 0 && typeof guard !== "function") throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" guard must be a function.`);
5556
- const reduce = readOptionalDomainMachineDefinitionProperty(transition, "reduce");
5557
- if (reduce !== void 0 && typeof reduce !== "function") throw new InvalidDomainMachineDefinitionError(`Domain transition from "${state}" on "${inputType}" reduce must be a function.`);
5558
- }
5559
- }
5560
- }
5561
- function assertDomainMachineDefinitionDataProperties(value, allowedKeys) {
5562
- for (const key of Reflect.ownKeys(value)) {
5563
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
5564
- if (descriptor !== void 0 && !("value" in descriptor)) throw new InvalidDomainMachineDefinitionError("Domain machine definition must contain data properties only.");
5565
- if (allowedKeys !== void 0 && !allowedKeys.has(key)) throw new InvalidDomainMachineDefinitionError(`Domain machine definition contains unknown property "${String(key)}".`);
5566
- }
5567
- }
5568
- function assertDomainMachineDefinitionEntryMap(value, entryName) {
5569
- assertDomainMachineDefinitionDataProperties(value);
5570
- for (const key of Reflect.ownKeys(value)) {
5571
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
5572
- if (typeof key !== "string" || descriptor?.enumerable !== true) throw new InvalidDomainMachineDefinitionError(`Domain machine ${entryName} names must be enumerable string properties.`);
5573
- }
5574
- }
5575
- function readDomainMachineDefinitionProperty(value, key) {
5576
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
5577
- if (descriptor === void 0 || !("value" in descriptor)) throw new InvalidDomainMachineDefinitionError("Domain machine definition must contain data properties only.");
5578
- return descriptor.value;
5579
- }
5580
- function readOptionalDomainMachineDefinitionProperty(value, key) {
5581
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
5582
- if (descriptor === void 0) return void 0;
5583
- if (!("value" in descriptor)) throw new InvalidDomainMachineDefinitionError("Domain machine definition must contain data properties only.");
5584
- return descriptor.value;
5585
- }
5586
-
5587
- //#endregion
5588
- //#region src/domain/state-machine/snapshot.ts
5589
- const DOMAIN_TRANSITION_RESULT_KEYS = /* @__PURE__ */ new Set(["context", "outputs"]);
5590
- function prepareDomainMachineSnapshot(definition, snapshot) {
5591
- validateDomainMachineSnapshot(definition, snapshot);
5592
- const preparedSnapshot = createDomainMachineSnapshot(snapshot);
5593
- validateDomainMachineSnapshot(definition, preparedSnapshot);
5594
- validateDomainMachineSnapshotInvariant(definition, preparedSnapshot);
5595
- return preparedSnapshot;
5596
- }
5597
- function createDomainMachineSnapshotFromPreparedContext(state, context) {
5598
- return Object.freeze({
5599
- state,
5600
- context
5601
- });
5602
- }
5603
- function createDomainMachineSnapshot(snapshot) {
5604
- return Object.freeze({
5605
- state: readDomainMachineSnapshotState(snapshot),
5606
- context: copyDomainMachineContext(readDomainMachineSnapshotContext(snapshot))
5607
- });
5608
- }
5609
- function validateDomainMachineSnapshot(definition, snapshot) {
5610
- if (!isRecord(snapshot)) throw new InvalidDomainMachineSnapshotError("Domain machine snapshot must be an object.");
5611
- const state = readDomainMachineSnapshotState(snapshot);
5612
- readDomainMachineSnapshotContext(snapshot);
5613
- if (!hasOwn(definition.states, state)) throw new InvalidDomainMachineSnapshotError(`Domain machine snapshot state "${state}" is not defined.`);
5614
- }
5615
- function validateDomainMachineSnapshotInvariant(definition, snapshot) {
5616
- const stateNode = definition.states[snapshot.state];
5617
- if (stateNode.validateContext !== void 0) {
5618
- const validContext = stateNode.validateContext({
5619
- state: snapshot.state,
5620
- context: snapshot.context
5621
- });
5622
- if (typeof validContext !== "boolean") throw new InvalidDomainMachineDefinitionError(`Domain machine state "${snapshot.state}" validateContext must return a boolean.`);
5623
- if (!validContext) throw new InvalidDomainMachineSnapshotError(`Domain machine snapshot violates the context invariant for state "${snapshot.state}".`);
5624
- }
5625
- if (definition.validateSnapshot === void 0) return;
5626
- const valid = definition.validateSnapshot(snapshot);
5627
- if (typeof valid !== "boolean") throw new InvalidDomainMachineDefinitionError("Domain machine validateSnapshot must return a boolean.");
5628
- if (!valid) throw new InvalidDomainMachineSnapshotError(`Domain machine snapshot violates invariants for state "${snapshot.state}".`);
5629
- }
5630
- function readDomainMachineSnapshotState(snapshot) {
5631
- const stateDescriptor = Object.getOwnPropertyDescriptor(snapshot, "state");
5632
- if (stateDescriptor === void 0 || !("value" in stateDescriptor) || typeof stateDescriptor.value !== "string") throw new InvalidDomainMachineSnapshotError("Domain machine snapshot state must be a string data property.");
5633
- return stateDescriptor.value;
5634
- }
5635
- function readDomainMachineSnapshotContext(snapshot) {
5636
- const contextDescriptor = Object.getOwnPropertyDescriptor(snapshot, "context");
5637
- if (contextDescriptor === void 0 || !("value" in contextDescriptor)) throw new InvalidDomainMachineSnapshotError("Domain machine snapshot context must be present as a data property.");
5638
- return contextDescriptor.value;
5639
- }
5640
- function validateDomainMachineInput(input) {
5641
- if (!isDomainMachineInput(input)) throw new InvalidDomainMachineInputError("Domain machine input must be an object with a string type.");
5642
- }
5643
- function isDomainMachineInput(input) {
5644
- if (!isRecord(input)) return false;
5645
- const typeDescriptor = Object.getOwnPropertyDescriptor(input, "type");
5646
- return typeDescriptor !== void 0 && "value" in typeDescriptor && typeof typeDescriptor.value === "string";
5647
- }
5648
- function resolveDomainTransitionGuardResult(result) {
5649
- if (typeof result === "boolean") return { allowed: result };
5650
- if (result instanceof DomainError) return {
5651
- allowed: false,
5652
- rejection: result
5653
- };
5654
- throw new InvalidDomainTransitionGuardResultError("Domain transition guard must return a boolean or DomainError.");
5655
- }
5656
- function validateDomainTransitionResult(result) {
5657
- if (result === void 0) return;
5658
- if (!isPlainRecord(result)) throw new InvalidDomainTransitionResultError("Domain transition result must be a plain object when returned.");
5659
- for (const key of Reflect.ownKeys(result)) {
5660
- if (!DOMAIN_TRANSITION_RESULT_KEYS.has(key)) throw new InvalidDomainTransitionResultError(`Domain transition result contains unknown property "${String(key)}".`);
5661
- const descriptor = Object.getOwnPropertyDescriptor(result, key);
5662
- if (descriptor !== void 0 && !("value" in descriptor)) throw new InvalidDomainTransitionResultError("Domain transition result must contain data properties only.");
5663
- }
5664
- const outputs = readDomainTransitionResultOutputs(result);
5665
- if (outputs !== void 0 && !Array.isArray(outputs)) throw new InvalidDomainTransitionResultError("Domain transition result outputs must be an array when provided.");
5666
- }
5667
- function readDomainTransitionResultContext(result) {
5668
- if (result === void 0) return { hasContext: false };
5669
- const contextDescriptor = Object.getOwnPropertyDescriptor(result, "context");
5670
- if (contextDescriptor === void 0) return { hasContext: false };
5671
- if (!("value" in contextDescriptor)) throw new InvalidDomainTransitionResultError("Domain transition result context must be a data property when provided.");
5672
- return {
5673
- hasContext: true,
5674
- context: contextDescriptor.value
5675
- };
5676
- }
5677
- function readDomainTransitionResultOutputs(result) {
5678
- if (result === void 0) return void 0;
5679
- const outputsDescriptor = Object.getOwnPropertyDescriptor(result, "outputs");
5680
- if (outputsDescriptor === void 0) return void 0;
5681
- if (!("value" in outputsDescriptor)) throw new InvalidDomainTransitionResultError("Domain transition result outputs must be a data property when provided.");
5682
- return outputsDescriptor.value;
5683
- }
5684
-
5685
- //#endregion
5686
- //#region src/domain/state-machine/transition.ts
5687
- /** Creates and validates a fresh initial snapshot from a machine definition. */
5688
- function createInitialDomainMachineSnapshot(definition) {
5689
- return createInitialDomainMachineSnapshotFromPrepared(ensureStableDomainMachineDefinition(definition));
5690
- }
5691
- function createInitialDomainMachineSnapshotFromPrepared(definition) {
5692
- const snapshot = createDomainMachineSnapshot({
5693
- state: definition.initial,
5694
- context: definition.initialContext()
5695
- });
5696
- validateDomainMachineSnapshotInvariant(definition, snapshot);
5697
- return snapshot;
5698
- }
5699
- /**
5700
- * Checks whether an input currently has an allowed transition.
5701
- *
5702
- * Returns `false` for missing transitions, terminal states, rejected guards,
5703
- * and inputs without an own string `type` property. Invalid payload data for a
5704
- * matching transition and broken guard code still throw structured errors.
5705
- */
5706
- function canTransitionDomainState(definition, snapshot, input) {
5707
- const stableDefinition = ensureStableDomainMachineDefinition(definition);
5708
- return canTransitionPreparedDomainState(stableDefinition, prepareDomainMachineSnapshot(stableDefinition, snapshot), input);
5709
- }
5710
- function canTransitionPreparedDomainState(definition, snapshot, input) {
5711
- if (!isDomainMachineInput(input)) return false;
5712
- if (definition.states[snapshot.state].terminal === true) return false;
5713
- const transition = getTransition(definition, snapshot.state, input);
5714
- if (!transition) return false;
5715
- const currentInput = copyDomainMachineInput(input);
5716
- if (!transition.guard) return true;
5717
- const guardResult = transition.guard({
5718
- state: snapshot.state,
5719
- context: snapshot.context,
5720
- input: currentInput
5721
- });
5722
- return resolveDomainTransitionGuardResult(guardResult).allowed;
5723
- }
5724
- /**
5725
- * Applies one input without mutating the input definition or snapshot.
5726
- *
5727
- * @throws {@link InvalidDomainTransitionError} when no transition is defined.
5728
- * @throws {@link DomainTransitionGuardRejectedError} when its guard rejects.
5729
- * @throws A concrete `DomainError` returned by a rejecting guard.
5730
- */
5731
- function transitionDomainState(definition, snapshot, input) {
5732
- const stableDefinition = ensureStableDomainMachineDefinition(definition);
5733
- return transitionPreparedDomainState(stableDefinition, prepareDomainMachineSnapshot(stableDefinition, snapshot), input);
5734
- }
5735
- function transitionPreparedDomainState(definition, snapshot, input) {
5736
- validateDomainMachineInput(input);
5737
- const from = snapshot.state;
5738
- const transition = definition.states[from].terminal === true ? void 0 : getTransition(definition, from, input);
5739
- if (!transition) throw new InvalidDomainTransitionError(from, input.type);
5740
- const currentInput = copyDomainMachineInput(input);
5741
- const guardResult = transition.guard === void 0 ? true : transition.guard({
5742
- state: from,
5743
- context: snapshot.context,
5744
- input: currentInput
5745
- });
5746
- const guardDecision = resolveDomainTransitionGuardResult(guardResult);
5747
- if (!guardDecision.allowed) {
5748
- if (guardDecision.rejection !== void 0) throw guardDecision.rejection;
5749
- throw new DomainTransitionGuardRejectedError(from, currentInput.type);
5750
- }
5751
- const result = transition.reduce?.({
5752
- state: from,
5753
- context: snapshot.context,
5754
- input: currentInput
5755
- });
5756
- validateDomainTransitionResult(result);
5757
- const contextResult = readDomainTransitionResultContext(result);
5758
- const nextContext = contextResult.hasContext ? contextResult.context : snapshot.context;
5759
- const nextSnapshot = nextContext === snapshot.context ? createDomainMachineSnapshotFromPreparedContext(transition.target, snapshot.context) : createDomainMachineSnapshot({
5760
- state: transition.target,
5761
- context: nextContext
5762
- });
5763
- validateDomainMachineSnapshotInvariant(definition, nextSnapshot);
5764
- return Object.freeze({
5765
- from,
5766
- to: transition.target,
5767
- snapshot: nextSnapshot,
5768
- outputs: copyDomainMachineOutputs(readDomainTransitionResultOutputs(result))
5769
- });
5770
- }
5771
-
5772
- //#endregion
5773
- //#region src/domain/state-machine/analyzer.ts
5774
- /**
5775
- * Inspects the declarative transition graph without executing definition callbacks.
5776
- * Guarded edges are treated as possible edges, so diagnostics never claim more
5777
- * runtime reachability than the static graph can prove.
5778
- */
5779
- function analyzeDomainMachineDefinition(definition) {
5780
- const stableDefinition = ensureStableDomainMachineDefinition(definition);
5781
- const states = Object.keys(stableDefinition.states).sort(compareStrings);
5782
- const outgoing = /* @__PURE__ */ new Map();
5783
- const incoming = /* @__PURE__ */ new Map();
5784
- const transitions = [];
5785
- for (const state of states) {
5786
- outgoing.set(state, []);
5787
- incoming.set(state, []);
5788
- }
5789
- for (const state of states) {
5790
- const stateTransitions = stableDefinition.states[state].on;
5791
- const inputTypes = Object.keys(stateTransitions ?? {}).sort(compareStrings);
5792
- for (const inputType of inputTypes) {
5793
- const transition = stateTransitions?.[inputType];
5794
- if (transition === void 0) continue;
5795
- outgoing.get(state)?.push(transition.target);
5796
- incoming.get(transition.target)?.push(state);
5797
- transitions.push(Object.freeze({
5798
- state,
5799
- inputType,
5800
- target: transition.target,
5801
- guarded: transition.guard !== void 0
5802
- }));
5803
- }
5804
- }
5805
- const structurallyReachable = visitGraph([stableDefinition.initial], outgoing);
5806
- const statesWithTerminalPath = visitGraph(states.filter((state) => stableDefinition.states[state].terminal === true), incoming);
5807
- const diagnostics = [];
5808
- for (const state of states) if (!structurallyReachable.has(state)) diagnostics.push(Object.freeze({
5809
- code: "unreachable-state",
5810
- state
5811
- }));
5812
- for (const state of states) if (stableDefinition.states[state].terminal !== true && outgoing.get(state)?.length === 0) diagnostics.push(Object.freeze({
5813
- code: "structural-dead-end",
5814
- state
5815
- }));
5816
- for (const state of states) if (!statesWithTerminalPath.has(state)) diagnostics.push(Object.freeze({
5817
- code: "no-terminal-path",
5818
- state
5819
- }));
5820
- return Object.freeze({
5821
- diagnostics: Object.freeze(diagnostics),
5822
- transitions: Object.freeze(transitions),
5823
- structurallyReachableStates: Object.freeze(states.filter((state) => structurallyReachable.has(state))),
5824
- statesWithTerminalPath: Object.freeze(states.filter((state) => statesWithTerminalPath.has(state)))
5825
- });
5826
- }
5827
- function visitGraph(startStates, edges) {
5828
- const visited = /* @__PURE__ */ new Set();
5829
- const pending = [...startStates];
5830
- while (pending.length > 0) {
5831
- const state = pending.pop();
5832
- if (state === void 0 || visited.has(state)) continue;
5833
- visited.add(state);
5834
- for (const next of edges.get(state) ?? []) if (!visited.has(next)) pending.push(next);
5835
- }
5836
- return visited;
5837
- }
5838
- function compareStrings(left, right) {
5839
- return left < right ? -1 : left > right ? 1 : 0;
5840
- }
5841
-
5842
- //#endregion
5843
- //#region src/domain/state-machine/domain-state-machine.ts
5844
- /**
5845
- * Stateful convenience wrapper around the pure domain transition functions.
5846
- *
5847
- * Persist {@link snapshot}, not the machine instance. Pass a restored snapshot
5848
- * to the second constructor overload to validate and reconstitute a machine.
5849
- */
5850
- var DomainStateMachine = class {
5851
- definition;
5852
- #snapshot;
5853
- #evaluating = false;
5854
- constructor(definition, ...snapshotInput) {
5855
- this.definition = ensureStableDomainMachineDefinition(definition);
5856
- const [suppliedSnapshot] = snapshotInput;
5857
- if (suppliedSnapshot !== void 0) this.#snapshot = prepareDomainMachineSnapshot(this.definition, suppliedSnapshot);
5858
- else this.#snapshot = createInitialDomainMachineSnapshotFromPrepared(this.definition);
5859
- }
5860
- /** Returns a defensive, deeply frozen copy of the current snapshot. */
5861
- get snapshot() {
5862
- return createDomainMachineSnapshot(this.#snapshot);
5863
- }
5864
- /** Current named control state. */
5865
- get state() {
5866
- return this.#snapshot.state;
5867
- }
5868
- /** Current deeply readonly context. */
5869
- get context() {
5870
- return this.#snapshot.context;
5871
- }
5872
- /** Whether the current state permanently forbids outgoing transitions. */
5873
- isTerminal() {
5874
- return this.definition.states[this.state].terminal === true;
5875
- }
5876
- /** Checks a transition without changing the current snapshot. */
5877
- can(input) {
5878
- return this.evaluate(() => canTransitionPreparedDomainState(this.definition, this.#snapshot, input));
5879
- }
5880
- /** Applies an input and advances the current snapshot on success. */
5881
- dispatch(input) {
5882
- return this.evaluate(() => {
5883
- const result = transitionPreparedDomainState(this.definition, this.#snapshot, input);
5884
- this.#snapshot = result.snapshot;
5885
- return result;
5886
- });
5887
- }
5888
- evaluate(operation) {
5889
- if (this.#evaluating) throw new ReentrantDomainStateMachineEvaluationError();
5890
- this.#evaluating = true;
5891
- try {
5892
- return operation();
5893
- } finally {
5894
- this.#evaluating = false;
5895
- }
5896
- }
5897
- };
5898
-
5899
- //#endregion
5900
- //#region src/domain/value-object/vo-validated.ts
5252
+ //#region src/domain/value-object/value-object-or-issues.ts
5901
5253
  /**
5902
5254
  * Builds an immutable value object while collecting **all** validation
5903
5255
  * violations into a single {@link ValidationError}, instead of failing on the
5904
5256
  * first one. This is the Result-first, multi-error counterpart to
5905
- * `voWithValidation` (which returns a single string message).
5257
+ * `valueObjectOrError` (which returns a single string message).
5906
5258
  *
5907
5259
  * The `validate` callback receives a fresh `ValidationError` to push field
5908
5260
  * issues onto (via `addIssue` / `addIssues`) and the raw input. When no issue
5909
- * was recorded the input is frozen into a `VO<T>` and returned as `Ok`;
5261
+ * was recorded the input is frozen into a `ValueObjectOf<T>` and returned as `Ok`;
5910
5262
  * otherwise the populated `ValidationError` is returned as `Err`.
5911
5263
  *
5912
5264
  * `ValidationError` comes from `@shirudo/base-error`; import it from there to
@@ -5916,7 +5268,7 @@ var DomainStateMachine = class {
5916
5268
  *
5917
5269
  * @example
5918
5270
  * ```ts
5919
- * const result = voValidated(
5271
+ * const result = valueObjectOrIssues(
5920
5272
  * { email, age },
5921
5273
  * (issues, m) => {
5922
5274
  * if (!isEmail(m.email))
@@ -5929,10 +5281,10 @@ var DomainStateMachine = class {
5929
5281
  * // result.isErr() → result.error.publicIssues() has both violations
5930
5282
  * ```
5931
5283
  */
5932
- function voValidated(t, validate, message = "Validation failed") {
5284
+ function valueObjectOrIssues(t, validate, message = "Validation failed") {
5933
5285
  const issues = new ValidationError(message);
5934
5286
  validate(issues, t);
5935
- return issues.hasIssues() ? err(issues) : ok(vo(t));
5287
+ return issues.hasIssues() ? err(issues) : ok(valueObject(t));
5936
5288
  }
5937
5289
 
5938
5290
  //#endregion
@@ -6041,7 +5393,7 @@ var PublishChainTracker = class {
6041
5393
  stateBySignal = /* @__PURE__ */ new WeakMap();
6042
5394
  syncWindowState;
6043
5395
  constructor(store) {
6044
- if (store !== void 0 && (typeof store.run !== "function" || typeof store.getStore !== "function")) throw new TypeError("EventBusImpl.chainStore must provide run and getStore");
5396
+ if (store !== void 0 && (typeof store.run !== "function" || typeof store.getStore !== "function")) throw new TypeError("InProcessEventBus.chainStore must provide run and getStore");
6045
5397
  this.store = store;
6046
5398
  }
6047
5399
  /** Depth, path and enclosing state that a publication on `signal` inherits. */
@@ -6152,7 +5504,7 @@ function toHandlerError(reason) {
6152
5504
  /** Bound for one publish chain. Real nesting stays far below this. */
6153
5505
  const DEFAULT_MAX_PUBLISH_DEPTH = 32;
6154
5506
  /** Marks the catch-all subscriptions in the reported-once set. */
6155
- const CATCH_ALL = Symbol("EventBusImpl.subscribeAll");
5507
+ const CATCH_ALL = Symbol("InProcessEventBus.subscribeAll");
6156
5508
  /**
6157
5509
  * Subscriptions for one event type above which the bus reports a leak. A
6158
5510
  * fan-out of projections stays far below this.
@@ -6166,7 +5518,7 @@ const DEFAULT_MAX_SUBSCRIPTIONS_PER_EVENT_TYPE = 32;
6166
5518
  *
6167
5519
  * @example
6168
5520
  * ```typescript
6169
- * const bus = new EventBusImpl<OrderEvent>();
5521
+ * const bus = new InProcessEventBus<OrderEvent>();
6170
5522
  *
6171
5523
  * bus.subscribe("OrderCreated", async (event) => {
6172
5524
  * await sendEmail(event.payload.customerId);
@@ -6226,7 +5578,7 @@ const DEFAULT_MAX_SUBSCRIPTIONS_PER_EVENT_TYPE = 32;
6226
5578
  * the signal.
6227
5579
  *
6228
5580
  */
6229
- var EventBusImpl = class {
5581
+ var InProcessEventBus = class {
6230
5582
  handlers = /* @__PURE__ */ new Map();
6231
5583
  catchAllHandlers = [];
6232
5584
  maxPublishDepth;
@@ -6237,12 +5589,12 @@ var EventBusImpl = class {
6237
5589
  observers;
6238
5590
  reportedAt = /* @__PURE__ */ new Map();
6239
5591
  constructor(options = {}) {
6240
- if (options.maxPublishDepth !== void 0) assertPositiveInteger("EventBusImpl", "maxPublishDepth", options.maxPublishDepth);
6241
- if (options.maxSubscriptionsPerEventType !== void 0) assertPositiveInteger("EventBusImpl", "maxSubscriptionsPerEventType", options.maxSubscriptionsPerEventType);
5592
+ if (options.maxPublishDepth !== void 0) assertPositiveInteger("InProcessEventBus", "maxPublishDepth", options.maxPublishDepth);
5593
+ if (options.maxSubscriptionsPerEventType !== void 0) assertPositiveInteger("InProcessEventBus", "maxSubscriptionsPerEventType", options.maxSubscriptionsPerEventType);
6242
5594
  this.chain = new PublishChainTracker(options.chainStore);
6243
5595
  this.maxPublishDepth = options.maxPublishDepth ?? DEFAULT_MAX_PUBLISH_DEPTH;
6244
5596
  this.maxSubscriptionsPerEventType = options.maxSubscriptionsPerEventType ?? DEFAULT_MAX_SUBSCRIPTIONS_PER_EVENT_TYPE;
6245
- this.observers = options.observers === void 0 ? void 0 : captureObserverFunctions("EventBusImpl", options.observers, [
5597
+ this.observers = options.observers === void 0 ? void 0 : captureObserverFunctions("InProcessEventBus", options.observers, [
6246
5598
  "onSubscriptionThresholdExceeded",
6247
5599
  "onHandlerError",
6248
5600
  "onPublishAborted"
@@ -6515,7 +5867,7 @@ function decodeIntegrationMessage(serialized) {
6515
5867
  }
6516
5868
  }
6517
5869
  /**
6518
- * Composes a validated public message into a minted local projector input.
5870
+ * Composes a validated public message into a kit-built local projector input.
6519
5871
  * Relationship headers become local event metadata. The public JSON schema is
6520
5872
  * retained; producer-private domain types are not reconstructed.
6521
5873
  */
@@ -6668,9 +6020,9 @@ function outboxWriterAcceptingEventLoss() {
6668
6020
  * another aggregate version. Without an `InMemoryTransactionScope`, it keeps
6669
6021
  * the first add of a rolled-back attempt. Dead-lettered and acknowledged
6670
6022
  * retries must match the complete original candidate receipt. Reusing an `eventId` for another source or commit position
6671
- * throws {@link EventHarvestError} while the pending, dead-letter, or bounded
6023
+ * throws {@link EventCollectionError} while the pending, dead-letter, or bounded
6672
6024
  * dispatched receipt still proves the collision. `add` also throws
6673
- * {@link EventHarvestError} for a malformed candidate: a part that is not an
6025
+ * {@link EventCollectionError} for a malformed candidate: a part that is not an
6674
6026
  * object, an `eventId` or `type` that is not an own string data property, a
6675
6027
  * source field that is not a string, or a position that is not a valid
6676
6028
  * commit position. Insertion order is preserved:
@@ -6685,7 +6037,7 @@ function outboxWriterAcceptingEventLoss() {
6685
6037
  * To link future eventful commits, the implementation also retains one
6686
6038
  * source cursor per qualified aggregate after dispatch. `endEventSources`
6687
6039
  * marks the cursor of a removed aggregate as ended, and a new event of that
6688
- * source then throws {@link EventHarvestError}. Consequently a
6040
+ * source then throws {@link EventCollectionError}. Consequently a
6689
6041
  * long-lived instance is bounded only when `maxRecords` and `maxSources` are
6690
6042
  * configured, plus `maxRetainedDispatchedEventIds`; use a durable adapter with
6691
6043
  * an explicit source-head lifecycle and an event-id unique key for unbounded
@@ -6717,10 +6069,10 @@ function outboxWriterAcceptingEventLoss() {
6717
6069
  *
6718
6070
  * @example
6719
6071
  * ```ts
6720
- * import { InMemoryOutbox, EventBusImpl } from "@shirudo/ddd-kit";
6072
+ * import { InMemoryOutbox, InProcessEventBus } from "@shirudo/ddd-kit";
6721
6073
  *
6722
6074
  * const outbox = new InMemoryOutbox<OrderEvent>();
6723
- * const bus = new EventBusImpl<OrderEvent>();
6075
+ * const bus = new InProcessEventBus<OrderEvent>();
6724
6076
  *
6725
6077
  * const uow = makeOrderUnitOfWork({ outbox, bus });
6726
6078
  * await uow.run(async ({ repositories }) => {
@@ -7022,7 +6374,7 @@ var InMemoryOutbox = class {
7022
6374
  if (this.pending.has(event.eventId) || this.dead.has(event.eventId) || this.dispatchedEventIds.has(event.eventId)) return;
7023
6375
  const cursor = this.sourceCursors.get(sourceKey);
7024
6376
  if (cursor !== void 0 && (position.aggregateVersion < cursor.aggregateVersion || position.aggregateVersion === cursor.aggregateVersion && cursor.eventIdsBySequence.has(position.commitSequence))) return;
7025
- throw new EventHarvestError(`InMemoryOutbox rejected event "${event.eventId}" for ${describeAggregateIdentity(source)}: the aggregate was removed, so its event source ended. The kit does not support an aggregate that is created again under a removed identity. Give the new aggregate a new id.`, event.type);
6377
+ throw new EventCollectionError(`InMemoryOutbox rejected event "${event.eventId}" for ${describeAggregateIdentity(source)}: the aggregate was removed, so its event source ended. The kit does not support an aggregate that is created again under a removed identity. Give the new aggregate a new id.`, event.type);
7026
6378
  }
7027
6379
  /**
7028
6380
  * Event ids of the batch that the source head names at their position
@@ -7091,7 +6443,7 @@ function assertSameCandidateReceipt(event, received, recorded) {
7091
6443
  }
7092
6444
  /**
7093
6445
  * The lenient variant for PENDING records only: an outbox without an
7094
- * `InMemoryTransactionScope` cannot observe a rollback, so a re-harvested
6446
+ * `InMemoryTransactionScope` cannot observe a rollback, so a re-collected
7095
6447
  * event may legitimately arrive at a new aggregateVersion. Index and commit
7096
6448
  * cardinality stay immutable.
7097
6449
  */
@@ -7100,7 +6452,7 @@ function assertSameCandidateReceiptAllowingVersionRefresh(event, received, recor
7100
6452
  }
7101
6453
  function assertReceiptShape(event, received, recorded, allowAggregateVersionRefresh) {
7102
6454
  if ((allowAggregateVersionRefresh || received.aggregateVersion === recorded.aggregateVersion) && received.commitSequence === recorded.commitSequence && received.commitSize === recorded.commitSize) return;
7103
- throw new EventHarvestError(`InMemoryOutbox rejected event "${event.eventId}": its commit candidate changed from (${recorded.aggregateVersion}, ${recorded.commitSequence}; commitSize=${recorded.commitSize}) to (${received.aggregateVersion}, ${received.commitSequence}; commitSize=${received.commitSize}). An exact redelivery must keep its source position immutable.`, event.type);
6455
+ throw new EventCollectionError(`InMemoryOutbox rejected event "${event.eventId}": its commit candidate changed from (${recorded.aggregateVersion}, ${recorded.commitSequence}; commitSize=${recorded.commitSize}) to (${received.aggregateVersion}, ${received.commitSequence}; commitSize=${received.commitSize}). An exact redelivery must keep its source position immutable.`, event.type);
7104
6456
  }
7105
6457
  /**
7106
6458
  * The fields of a candidate that the outbox reads, read once and checked.
@@ -7112,7 +6464,7 @@ function assertReceiptShape(event, received, recorded, allowAggregateVersionRefr
7112
6464
  function ownCandidate(candidate, index) {
7113
6465
  let subject = `the candidate at index ${index}`;
7114
6466
  let eventType;
7115
- const rejection = (field, expected, observed) => new EventHarvestError(`InMemoryOutbox rejected ${subject}: ${field} must be ${expected}, but it is ${observed}. Pass the candidates that withCommit builds.`, eventType);
6467
+ const rejection = (field, expected, observed) => new EventCollectionError(`InMemoryOutbox rejected ${subject}: ${field} must be ${expected}, but it is ${observed}. Pass the candidates that withCommit builds.`, eventType);
7116
6468
  if (typeof candidate !== "object" || candidate === null) throw rejection("the candidate", "an object", describeRejected(candidate));
7117
6469
  const { event, source, position } = candidate;
7118
6470
  if (typeof event !== "object" || event === null) throw rejection("event", "an object", describeRejected(event));
@@ -7162,17 +6514,17 @@ function refill(map, entries) {
7162
6514
  for (const [key, value] of entries) map.set(key, value);
7163
6515
  }
7164
6516
  function commitSizeMismatchError(event, source, position, recordedCommitSize) {
7165
- return new EventHarvestError(`InMemoryOutbox rejected event "${event.eventId}" for ${describeAggregateIdentity(source)}: aggregate version ${position.aggregateVersion} was already recorded with commitSize ${recordedCommitSize}, not ${position.commitSize}. An aggregate that was removed and created again under the same identity causes this too; the kit does not support that, so give the new aggregate a new id.`, event.type);
6517
+ return new EventCollectionError(`InMemoryOutbox rejected event "${event.eventId}" for ${describeAggregateIdentity(source)}: aggregate version ${position.aggregateVersion} was already recorded with commitSize ${recordedCommitSize}, not ${position.commitSize}. An aggregate that was removed and created again under the same identity causes this too; the kit does not support that, so give the new aggregate a new id.`, event.type);
7166
6518
  }
7167
6519
  function positionOwnedError(event, source, position, positionOwner) {
7168
- return new EventHarvestError(`InMemoryOutbox rejected event "${event.eventId}" for ${describeAggregateIdentity(source)}: source position (${position.aggregateVersion}, ${position.commitSequence}) is already owned by event "${positionOwner}". One qualified source position must identify exactly one immutable event. An aggregate that was removed and created again under the same identity causes this too; the kit does not support that, so give the new aggregate a new id.`, event.type);
6520
+ return new EventCollectionError(`InMemoryOutbox rejected event "${event.eventId}" for ${describeAggregateIdentity(source)}: source position (${position.aggregateVersion}, ${position.commitSequence}) is already owned by event "${positionOwner}". One qualified source position must identify exactly one immutable event. An aggregate that was removed and created again under the same identity causes this too; the kit does not support that, so give the new aggregate a new id.`, event.type);
7169
6521
  }
7170
6522
  function staleHeadError(event, source, position, staleHeadVersion) {
7171
- return new EventHarvestError(`InMemoryOutbox rejected stale event "${event.eventId}" for ${describeAggregateIdentity(source)} at aggregate version ${position.aggregateVersion}: the event-source head is already ${staleHeadVersion}. There are two possible causes. First, the aggregate was removed and created again under the same identity. The kit does not support this, because the versions of the new aggregate restart below the head. Give the new aggregate a new id. Second, this is a retry of an event whose dispatched-id receipt expired. Use a durable outbox with a transactional eventId unique key for unbounded idempotency.`, event.type);
6523
+ return new EventCollectionError(`InMemoryOutbox rejected stale event "${event.eventId}" for ${describeAggregateIdentity(source)} at aggregate version ${position.aggregateVersion}: the event-source head is already ${staleHeadVersion}. There are two possible causes. First, the aggregate was removed and created again under the same identity. The kit does not support this, because the versions of the new aggregate restart below the head. Give the new aggregate a new id. Second, this is a retry of an event whose dispatched-id receipt expired. Use a durable outbox with a transactional eventId unique key for unbounded idempotency.`, event.type);
7172
6524
  }
7173
6525
  function assertSameEventSource(event, received, recorded) {
7174
6526
  if (received.aggregateType === recorded.aggregateType && received.aggregateId === recorded.aggregateId) return;
7175
- throw new EventHarvestError(`InMemoryOutbox rejected eventId collision for "${event.eventId}": it already belongs to ${describeAggregateIdentity(recorded)}, but was received for ${describeAggregateIdentity(received)}. An eventId must identify one immutable event across all aggregate sources.`, event.type);
6527
+ throw new EventCollectionError(`InMemoryOutbox rejected eventId collision for "${event.eventId}": it already belongs to ${describeAggregateIdentity(recorded)}, but was received for ${describeAggregateIdentity(received)}. An eventId must identify one immutable event across all aggregate sources.`, event.type);
7176
6528
  }
7177
6529
 
7178
6530
  //#endregion
@@ -7433,7 +6785,10 @@ var OutboxDispatcher = class extends PollLoop {
7433
6785
  function copyForStorage(event, index, stream) {
7434
6786
  const subject = `InMemoryEventStore.append (stream ${describeAggregateIdentity(stream)}): events[${index}]`;
7435
6787
  if (event === null || typeof event !== "object") throw new TypeError(`${subject} is not an event object`);
7436
- return detachFreezableState(event, subject);
6788
+ const copy = copyFreezableState(event, subject);
6789
+ const eventId = copy.eventId;
6790
+ if (typeof eventId !== "string" || eventId === "") throw new TypeError(`${subject} has no eventId. Stamp the event before the append: the commit stamps it when a write registers its aggregate, and stampPendingEvents stamps it before that.`);
6791
+ return copy;
7437
6792
  }
7438
6793
  function assertStreamPosition(name, value) {
7439
6794
  if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) throw new RangeError(`InMemoryEventStore: ${name} must be a non-negative safe integer, got ${String(value)}`);
@@ -7492,7 +6847,7 @@ var InMemoryEventStore = class {
7492
6847
  async append(stream, events, options) {
7493
6848
  const batch = Array.from(events);
7494
6849
  if (batch.length === 0) return;
7495
- const identity = detachAggregateIdentity(stream);
6850
+ const identity = copyAggregateIdentity(stream);
7496
6851
  const expectedVersion = options.expectedVersion;
7497
6852
  const key = encodeAggregateIdentity(identity);
7498
6853
  this.existingStreamForAppend(identity, key, expectedVersion, batch.length);
@@ -7770,7 +7125,7 @@ function defaultSleep(ms, signal) {
7770
7125
  *
7771
7126
  * **Classification is by error, not by guesswork.** Only errors the
7772
7127
  * `isRetryable` predicate accepts are retried; everything else (a
7773
- * `DomainError`, `EventHarvestError`, `UnenrolledChangesError`,
7128
+ * `DomainError`, `EventCollectionError`, `UnregisteredChangesError`,
7774
7129
  * `DuplicateAggregateError`, a non-Error throw) surfaces immediately.
7775
7130
  * After `maxAttempts` the last error is rethrown unchanged, so a caller
7776
7131
  * can still match `ConcurrencyConflictError` and map it to HTTP 409.
@@ -7936,12 +7291,12 @@ var InMemorySnapshotStore = class {
7936
7291
  //#region src/persistence/snapshot-store/snapshot-model.ts
7937
7292
  /** Type-inference helper for declaring an adapter-owned snapshot model. */
7938
7293
  function defineSnapshotModel(model) {
7939
- const detached = Object.freeze({ ...model });
7940
- assertSnapshotModel(detached);
7941
- return detached;
7294
+ const copy = Object.freeze({ ...model });
7295
+ assertSnapshotModel(copy);
7296
+ return copy;
7942
7297
  }
7943
7298
  /**
7944
- * Captures a detached persistence envelope at an application-supplied time.
7299
+ * Captures an independent persistence envelope at an application-supplied time.
7945
7300
  * The application decides when snapshotting is worthwhile; this function does
7946
7301
  * not read a clock or perform I/O. The envelope is deep-frozen, its `state`
7947
7302
  * and its `snapshotAt` included: a write into the captured state or a
@@ -7952,7 +7307,7 @@ function defineSnapshotModel(model) {
7952
7307
  function captureAggregateSnapshot(model, aggregate, snapshotAt) {
7953
7308
  assertSnapshotModel(model);
7954
7309
  const recordedAt = copySnapshotAt(snapshotAt);
7955
- const state = detachState(model.capture(aggregate));
7310
+ const state = copyPlainState(model.capture(aggregate));
7956
7311
  return deepFreeze({
7957
7312
  state,
7958
7313
  version: aggregate.version,
@@ -7991,8 +7346,8 @@ function reconstituteAggregateFromSnapshot(model, id, snapshot) {
7991
7346
  let aggregate;
7992
7347
  try {
7993
7348
  let state;
7994
- if (storedSchemaVersion === model.schemaVersion) state = detachState(snapshot.state);
7995
- else if (model.migrate) state = detachState(model.migrate(detachState(snapshot.state), storedSchemaVersion));
7349
+ if (storedSchemaVersion === model.schemaVersion) state = copyPlainState(snapshot.state);
7350
+ else if (model.migrate) state = copyPlainState(model.migrate(copyPlainState(snapshot.state), storedSchemaVersion));
7996
7351
  else throw new SnapshotSchemaMismatchError({
7997
7352
  identity: {
7998
7353
  aggregateType: model.aggregateType,
@@ -8031,5 +7386,5 @@ function copySnapshotAt(snapshotAt) {
8031
7386
  }
8032
7387
 
8033
7388
  //#endregion
8034
- export { AggregateDeletedError, AggregateNotFoundError, AggregateTrackingError, CapabilityRegistryConflictError, CommandBus, CommitError, ConcurrencyConflictError, DeadlineProcessor, DirectStateMutationError, DomainError, DomainEventValidationError, DomainStateMachine, DomainTransitionGuardRejectedError, DuplicateAggregateError, DuplicateEventIdError, DuplicateHandlerRegistrationError, Entity, ErrorMapperFailedError, EventBusClosedError, EventBusImpl, EventHarvestError, EventSourcedAggregate, FoldReturnedNoStateError, ForeignEventError, HostileStateKeyError, IdempotencyClaimLostError, IdempotencyCompletionWithoutClaimError, IdempotencyInFlightError, IdempotencyKeyReuseError, IdempotencyReconciliationRequiredError, IdentityMap, InMemoryCapacityExceededError, InMemoryDeadlineStore, InMemoryEventStore, InMemoryIdempotencyStore, InMemoryOutbox, InMemoryProjectionCheckpointStore, InMemorySnapshotStore, InMemoryTransactionScope, InfrastructureError, InvalidCommandMessageError, InvalidDomainMachineContextError, InvalidDomainMachineDefinitionError, InvalidDomainMachineInputError, InvalidDomainMachineSnapshotError, InvalidDomainTransitionError, InvalidDomainTransitionGuardResultError, InvalidDomainTransitionResultError, InvalidEventStreamPageError, InvalidFlushStatementError, InvalidIntegrationMessageError, InvalidRepositoryAdapterError, InvalidRepositoryDefinitionError, InvalidVersionError, MisattributedEventError, MissingEntityIdError, MissingFoldError, MissingHandlerError, NestedUnitOfWorkError, OutboxDispatcher, PendingEventBatchMismatchError, PendingEventLimitExceededError, ProjectionGapError, ProjectionIdentityViolationError, ProjectionOrderViolationError, ProjectionReceiptViolationError, Projector, PublishDepthExceededError, QueryBus, ReentrantDomainStateMachineEvaluationError, ReentrantEventRecordingError, ReplayRejectedError, ReplayTargetMismatchError, RepositoryErrorMappingFailedError, RetryingTransactionScope, RollbackError, SnapshotCorruptedError, SnapshotSchemaMismatchError, SnapshotTimeValidationError, SnapshotVersionNotRestoredError, Specification, StateStoredAggregate, TransactionClosedError, UnenrolledChangesError, UnitOfWork, UnmanagedInstanceError, UnmintedEventError, UnprojectableEventError, UnregisteredHandlerError, UnreplayableAggregateError, ValueObject, analyzeDomainMachineDefinition, canTransitionDomainState, captureAggregateSnapshot, capturePersistenceBaseline, classifyConcurrencyConflict, copyMetadata, createDomainEvent, createDomainEventFactory, createDomainEventFromFacts, createInitialDomainMachineSnapshot, createIntegrationMessage, createUncommittedDomainEvent, decodeIntegrationMessage, deepEqual, deepEqualExcept, deepFreeze, deepOmit, defaultDomainEventFactory, defineRepository, defineSnapshotModel, derivePersistenceChanges, detachState, domainErrorToResult, encodeIntegrationMessage, entityIds, eventBusSink, findEntityById, freezeShallow, hasEntityId, ignoreProjectionEvent, insertPersistenceBaseline, integrationMessageToCommittedEvent, isDomainErrorLike, isInfrastructureErrorLike, isPositionAfter, isWiringErrorLike, mergeMetadata, outboxWriterAcceptingEventLoss, persistenceProjectionDrifted, pinTargetVersion, prepareDomainMachineDefinition, projectionFromHandlers, readStreamPages, recapturePersistenceBaseline, reconstituteAggregateFromHistory, reconstituteAggregateFromSnapshot, reconstituteAggregateFromStreamPages, recordDomainEvent, recordPendingEvents, removeEntityById, replaceEntityById, routeEventsToCommandOutbox, sameEntity, sameVersion, specification, toVersion, transitionDomainState, updateEntityById, versionedFlush, vo, voEquals, voEqualsExcept, voValidated, voWithValidation, withCommit, withIdempotentCommit };
7389
+ export { AggregateDeletedError, AggregateNotFoundError, AggregateTrackingError, CapabilityRegistryConflictError, CommandBus, CommitError, ConcurrencyConflictError, DeadlineProcessor, DirectStateMutationError, DomainError, DomainEventValidationError, DuplicateAggregateError, DuplicateEventIdError, DuplicateHandlerRegistrationError, Entity, ErrorMapperFailedError, EventBusClosedError, EventCollectionError, EventSourcedAggregate, FoldReturnedNoStateError, ForeignEventError, HandBuiltEventError, IdempotencyClaimLostError, IdempotencyCompletionWithoutClaimError, IdempotencyInFlightError, IdempotencyKeyReuseError, IdempotencyReconciliationRequiredError, IdentityMap, InMemoryCapacityExceededError, InMemoryDeadlineStore, InMemoryEventStore, InMemoryIdempotencyStore, InMemoryOutbox, InMemoryProjectionCheckpointStore, InMemorySnapshotStore, InMemoryTransactionScope, InProcessEventBus, InfrastructureError, InvalidCommandMessageError, InvalidEventStreamPageError, InvalidFlushStatementError, InvalidIntegrationMessageError, InvalidRepositoryAdapterError, InvalidRepositoryDefinitionError, InvalidVersionError, MisattributedEventError, MissingEntityIdError, MissingFoldError, MissingHandlerError, NestedUnitOfWorkError, OutboxDispatcher, PendingEventBatchMismatchError, PendingEventLimitExceededError, ProjectionGapError, ProjectionIdentityViolationError, ProjectionOrderViolationError, ProjectionReceiptViolationError, Projector, PrototypePollutionError, PublishDepthExceededError, QueryBus, ReentrantEventRecordingError, ReplayRejectedError, ReplayTargetMismatchError, RepositoryErrorMappingFailedError, RetryingTransactionScope, RollbackError, SnapshotCorruptedError, SnapshotSchemaMismatchError, SnapshotTimeValidationError, SnapshotVersionNotRestoredError, Specification, StateStoredAggregate, TransactionClosedError, UnitOfWork, UnmanagedInstanceError, UnprojectableEventError, UnregisteredChangesError, UnregisteredHandlerError, UnreplayableAggregateError, ValueObject, captureAggregateSnapshot, classifyConcurrencyConflict, copyMetadata, copyPlainState, createDomainEvent, createDomainEventFactory, createIntegrationMessage, createStampedDomainEvent, decodeIntegrationMessage, deepEqual, deepFreeze, deepOmit, defaultDomainEventFactory, defineRepository, defineSnapshotModel, domainErrorToResult, encodeIntegrationMessage, entityIds, eventBusSink, findEntityById, hasEntityId, ignoreProjectionEvent, integrationMessageToCommittedEvent, isDomainErrorLike, isInfrastructureErrorLike, isPositionAfter, isWiringErrorLike, mergeMetadata, outboxWriterAcceptingEventLoss, pinTargetVersion, projectionFromHandlers, readStreamPages, reconstituteAggregateFromHistory, reconstituteAggregateFromSnapshot, reconstituteAggregateFromStreamPages, removeEntityById, replaceEntityById, routeEventsToCommandOutbox, sameEntity, sameVersion, specification, stampPendingEvents, toVersion, updateEntityById, valueObject, valueObjectEquals, valueObjectEqualsExcept, valueObjectOrError, valueObjectOrIssues, versionedFlush, withCommit, withIdempotentCommit };
8035
7390
  //# sourceMappingURL=index.js.map