Skip to content

RootEntity

Defined in: packages/ddd-core/domain/models/root.entity.ts:76

Abstract base entity for DDD domain aggregates and entities.

Inherits from AggregateRoot to manage domain events internally:

  • Domain Event Recording: Call this.apply(new SomeEvent(this)) to buffer uncommitted events.
  • UUID v7 Identity: Automatically generates time-ordered UUID v7 identifiers for new instances.
  • Lifecycle Timestamps: Enforces invariant-checked createdAt and updatedAt tracking.
  • Accessor-Driven Persistence: id, createdAt and updatedAt have public getters and private setters. An ORM hydrates through the setters (MikroORM: accessor: true); application code cannot assign them and changes state through factories and domain methods.
  • Optimistic Concurrency Control: Tracks integer aggregate versioning (_version, getExpectedVersion), incremented automatically on mutations to prevent concurrent lost updates.
  • Polymorphic Rehydration: Static RootEntity.from() transparently handles instances, plain snapshots, or nullish database results while enforcing strict aggregate type safety (throws TypeError on incompatible aggregates).
  • Mutation Tracking: Automatically updates updatedAt, increments _version on @ApplyMutation()-decorated methods, and triggers the afterUpdate() lifecycle hook.

Defining a domain aggregate

interface UserSnapshot extends Partial<RootEntitySnapshot> {
readonly username: string;
readonly email: string;
readonly version?: number;
}
export class User extends RootEntity<UserSnapshot> {
@Mutable<string>()
private _username: string;
readonly email: string;
private constructor(snapshot: UserSnapshot) {
super(snapshot);
this._username = snapshot.username!;
this.email = snapshot.email!;
}
static create(username: string, email: string): User {
const user = new User({ username, email });
user.apply(new UserCreatedEvent(user));
return user;
}
static fromJSON(snapshot: UserSnapshot): User {
return new User(snapshot);
}
@ApplyMutation<User>({ event: (user) => new UserRenamedEvent(user) })
rename(newUsername: string): this {
this.applyPatch({ username: newUsername });
return this;
}
toJSON(): RootEntitySnapshot & UserSnapshot {
return this.freezeState({
id: this.id,
username: this._username,
email: this.email,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
version: this._version,
});
}
}

TSnapshot extends Partial<RootEntitySnapshot> = RootEntitySnapshot

new RootEntity<TSnapshot>(snapshot?): RootEntity<TSnapshot>

Defined in: packages/ddd-core/domain/models/root.entity.ts:88

Partial<RootEntitySnapshot>

RootEntity<TSnapshot>

AggregateRoot.constructor

protected _persistedVersion: number

Defined in: packages/ddd-core/domain/models/root.entity.ts:86


protected _version: number

Defined in: packages/ddd-core/domain/models/root.entity.ts:85

get autoCommit(): boolean

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:43

Gets whether the aggregate root automatically commits and publishes events upon application.

boolean

set autoCommit(value): void

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:36

Sets whether the aggregate root should automatically commit and publish events upon application.

boolean

void

When true, apply() publishes each event at once instead of buffering it.

AggregateRoot.autoCommit


get createdAt(): Date

Defined in: packages/ddd-core/domain/models/root.entity.ts:208

Date

set createdAt(value): void

Defined in: packages/ddd-core/domain/models/root.entity.ts:216

For ORM hydration only (MikroORM: accessor: true). Private, so application code cannot assign it and changes state through domain methods and factories.

string | Date

void

RootEntitySnapshot.createdAt


get id(): string

Defined in: packages/ddd-core/domain/models/root.entity.ts:196

string

set id(value): void

Defined in: packages/ddd-core/domain/models/root.entity.ts:204

For ORM hydration only (MikroORM: accessor: true). Private, so application code cannot assign it and changes state through domain methods and factories.

string

void

RootEntitySnapshot.id


get updatedAt(): Date

Defined in: packages/ddd-core/domain/models/root.entity.ts:220

Date

set updatedAt(value): void

Defined in: packages/ddd-core/domain/models/root.entity.ts:228

For ORM hydration only (MikroORM: accessor: true). Private, so application code cannot assign it and changes state through domain methods and factories.

string | Date

void

RootEntitySnapshot.updatedAt


get version(): number

Defined in: packages/ddd-core/domain/models/root.entity.ts:244

The current in-memory version: 1 for a new entity, advanced by each @ApplyMutation() method. Persistence writes store it; the version a write expects to find in storage is getExpectedVersion.

const user = User.create('ada', 'ada@example.com'); // user.version === 1
user.rename('ada.l'); // user.version === 2
user.getExpectedVersion(); // still 1 until persisted

number

set version(value): void

Defined in: packages/ddd-core/domain/models/root.entity.ts:254

For ORM hydration only (MikroORM: accessor: true). Private, so application code cannot assign it and changes state through domain methods and factories. A loaded version is also the persisted baseline; a value that is not a positive integer is ignored.

number

void

RootEntitySnapshot.version

acknowledgePersisted(version?): void

Defined in: packages/ddd-core/domain/models/root.entity.ts:315

Acknowledges that a version has been successfully persisted to durable storage. Advances the expected version baseline (_persistedVersion) to match the version actually written (by default this._version), without recording a domain event or advancing the in-memory version.

This method is owned by persistence repositories and is typically invoked automatically by the @AcknowledgePersisted method decorator upon successful write resolution. Application code must never call this method directly; state mutations belong in domain methods.

number

Optional specific version that was persisted. If omitted, this._version is used. Must be a positive integer (> 0).

void

If version is provided but is not a positive integer.

Manual invocation in a custom persistence repository

async save(user: User): Promise<UserSnapshot> {
const snapshot = user.toJSON();
await this.db.update(user.id, snapshot);
user.acknowledgePersisted(); // advances _persistedVersion to user.version
return snapshot;
}

Declarative invocation through the AcknowledgePersisted decorator (recommended)

@AcknowledgePersisted<[User]>({ entity: ([user]) => user })
async save(user: User): Promise<UserSnapshot> {
// Decorator captures user.version before execution and calls
// user.acknowledgePersisted() when the returned promise resolves.
return snapshot;
}

protected afterUpdate(): void

Defined in: packages/ddd-core/domain/models/root.entity.ts:404

Post-mutation lifecycle hook, invoked after the version and updatedAt advance and before the mutation’s event is recorded. The default does nothing; override it only when the aggregate has post-mutation work.

void


apply<T>(event, optionsOrIsFromHistory?): void

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:138

Applies an event to the aggregate root.

If fromHistory is false and autoCommit is disabled, the event is appended to the uncommitted events buffer. If autoCommit is enabled, the event is published immediately. Unless skipHandler is true, routes to on<EventName>(event) if present.

T extends IEvent = IEvent

T

The domain event to apply.

boolean | ApplyEventOptions

Boolean indicating historical event or options object.

void

AggregateRoot.apply


protected applyPatch<TEntity>(patch): void

Defined in: packages/ddd-core/domain/models/root.entity.ts:347

Applies a patch to fields registered with @Mutable(), ignoring undefined values. Validates all keys and normalizes all values before writing any field. Does not advance version/timestamps or record events; use inside a decorated mutation.

TEntity extends object

MutationPatch<TEntity>

Public field names and proposed values; keys must be registered with @Mutable().

void

When a defined patch key is not registered.

Propagates field normalization errors without applying the patch.

Inside a User aggregate with a mutable username field

@ApplyMutation<User>({ event: (user) => new UserUpdatedEvent(user) })
update(username: string): this {
this.applyPatch<User>({ username });
return this;
}

commit(dispatcherContext?): unknown

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:95

Hands a copy of the buffered events to publishAll with the dispatcher context, then clears the buffer. The buffer is cleared once publishAll() returns, before an asynchronous publisher settles, so a caller that does not await cannot publish the same events twice; if publishAll() throws, the events stay buffered.

unknown

Passed to publishAll, such as { transaction }.

unknown

What publishAll returns: await it to wait for, and catch the errors of, an asynchronous publisher.

const order = publisher.mergeObjectContext(Order.place(id));
await order.commit({ transaction });

AggregateRoot.commit


protected freezeState<S>(state): Readonly<S>

Defined in: packages/ddd-core/domain/models/root.entity.ts:395

S extends object

S

Readonly<S>


protected getEventHandler<T>(event): ((event) => void) | undefined

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:171

Resolves the method handler corresponding to the applied event name (on<EventName>).

T extends IEvent = IEvent

T

((event) => void) | undefined

AggregateRoot.getEventHandler


protected getEventName(event): string

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:184

Resolves the constructor name of the event.

unknown

string

AggregateRoot.getEventName


getExpectedVersion(): number

Defined in: packages/ddd-core/domain/models/root.entity.ts:276

Returns the expected persistence version baseline (_persistedVersion).

This baseline represents the last confirmed durable version successfully written to persistent storage. Persistence adapters use this value in version predicates (e.g. WHERE id = ? AND version = expectedVersion) to detect concurrent updates.

number

The positive integer version expected in persistent storage.

const expected = user.getExpectedVersion(); // e.g. 1
await em.nativeUpdate(User, { id: user.id, version: expected }, { ...data, version: user.version });

getUncommittedEvents(): IEvent[]

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:114

Returns all uncommitted events currently buffered on this aggregate.

IEvent[]

AggregateRoot.getUncommittedEvents


loadFromHistory(history): void

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:122

Loads domain events from history to rehydrate the aggregate’s internal state. Historical events invoke event handlers without being re-buffered.

IEvent[]

void

AggregateRoot.loadFromHistory


protected onUpdate(): void

Defined in: packages/ddd-core/domain/models/root.entity.ts:389

void


publish<T>(_event, _dispatcherContext?): unknown

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:56

Called by apply() for each event while autoCommit is enabled; a no-op that returns undefined unless overridden or connected to a publisher (for example NestJS’s EventPublisher.mergeObjectContext).

T extends IEvent = IEvent

T

The event to publish.

unknown

Passed through to the publisher, such as { transaction }.

unknown

What the publisher returns.

AggregateRoot.publish


publishAll<T>(_events, _dispatcherContext?): unknown

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:71

Called by commit() with a copy of the buffered events; a no-op that returns undefined unless overridden or connected to a publisher.

T extends IEvent = IEvent

T[]

The events to publish.

unknown

Passed through to the publisher, such as { transaction }.

unknown

What the publisher returns.

AggregateRoot.publishAll


abstract toJSON(): RootEntitySnapshot & TSnapshot

Defined in: packages/ddd-core/domain/models/root.entity.ts:406

RootEntitySnapshot & TSnapshot


uncommit(): void

Defined in: packages/ddd-core/domain/models/aggregate-root.ts:107

Clears all uncommitted events without publishing.

void

AggregateRoot.uncommit


static from<T, TSnapshot>(this, candidate): T | null

Defined in: packages/ddd-core/domain/models/root.entity.ts:158

Rehydrates or returns an already-hydrated entity instance.

If candidate is already an instance of the target entity class, it is returned as-is. If candidate is an instance of an incompatible RootEntity, a TypeError is thrown. Otherwise, if candidate is a snapshot object, it is rehydrated via the class’s fromJSON() factory.

T extends RootEntity<TSnapshot>

TSnapshot extends Partial<RootEntitySnapshot>

{ fromJSON: T; } | ((…args) => T)

TSnapshot | T | null | undefined

T | null


protected static normalizeDate(value?): Date

Defined in: packages/ddd-core/domain/models/root.entity.ts:135

string | Date

Date


protected static normalizeId(id?): string

Defined in: packages/ddd-core/domain/models/root.entity.ts:128

string

string