Commit Pipeline
AggregateRoot.commit() coordinates one batch of uncommitted events. Persistence is the boundary in the middle of the pipeline: policy evaluation happens before it, while snapshot creation and subscription dispatch happen after it.
Exact execution order
- Copy the pending batch.
commit()shallow-copiesuncommittedEventsinto a localtoPublisharray. If the copy is empty, it returns the aggregate immediately. No publisher, adapter, snapshot policy, or subscription is called. - Resolve aggregate metadata. The publisher installed by
EventStore.addPublisher()reads the aggregate name from@AggregateRootConfig. A missing name rejects the commit before IDs are generated or storage is called. - Generate event IDs. The store calls
generateEntityId()once per event withPromise.all. A rejection, a missing value, or an unexpected result count rejects the commit. - Await the snapshot policy.
snapshotStore.shouldCreateSnapshot(aggregate)runs against the aggregate's current version and still-present uncommitted events. Event Nest awaits synchronous and asynchronous strategies before any event persistence. Atrueresult also validates that the class and instance are snapshot-aware. - Build two event representations. Each pending event becomes a
StoredEventfor the adapter and aPublishedDomainEventfor subscriptions. Both receive the generated event ID and retain the aggregate ID and occurrence time. - Call the adapter's
save. The store passes theStoredEvent[]and aStoredAggregateRootcarrying the aggregate ID and expected current version. Official adapters perform optimistic-concurrency checking, assign consecutive event versions, persist the events, and update the stored aggregate version. - Resolve the committed aggregate version. The highest version in the adapter's returned events becomes
aggregate.version. This happens immediately aftersaveresolves. - Create the snapshot when selected. Event Nest generates a snapshot ID, awaits
aggregate.toSnapshot(), and saves a snapshot containing the now-committed aggregate version, configured revision, payload, and aggregate ID. This write is after event persistence, not part of the event adapter'ssaveoperation. - Map committed event versions. Each
PublishedDomainEventis matched to a returnedStoredEventby event ID and receives its assignedaggregateRootVersion. A missing match raisesUnknownEventVersionException. - Dispatch subscriptions. The fully populated published events are passed to the
DomainEventEmitter. Dispatch begins only after event persistence and any selected snapshot creation have completed. - Clear or retain the aggregate buffer. When publishing resolves,
commit()clears all uncommitted events. It also clears them when publishing rejects withSubscriptionException, because storage has already succeeded. Every other rejection retains them.
The copied batch means subsequent appends are not added to the events already selected for this call. It is a shallow copy: payload objects themselves are not cloned.
The two representations
StoredEvent is storage-facing. StoredEvent.fromPublishedEvent() resolves the canonical domain-event name and uses instanceToPlain() from class-transformer for its payload. The adapter later assigns aggregateRootVersion.
PublishedDomainEvent is subscription-facing:
interface PublishedDomainEvent<T> {
aggregateRootId: string;
eventId: string;
occurredAt: Date;
payload: T;
version: number;
} Its payload remains the original class instance. Its preliminary version is replaced by the committed version returned by the adapter before dispatch.
Why policy evaluation is early
Policies such as ForCountSnapshotStrategy calculate a projected version from the current version plus uncommittedEvents.length. Resolving the aggregate to its committed version first would count the same events twice. Event Nest therefore awaits the decision before save, but creates the snapshot afterward so its metadata points at the committed stream head.
Persistence boundary
The official MongoDB, PostgreSQL, and SQL Server adapters keep their aggregate-row/document update and event inserts together in the adapter's transaction. Core only depends on the EventStore.save() contract and cannot make a custom adapter atomic.
Snapshot storage is a later operation, and subscriptions are later still. Neither should be described as part of, or capable of rolling back, the event persistence transaction. See Failure Behaviour for the observable state at each failure point and Subscription Dispatch for waiting rules.