@event-nest/core 4.0.1 → 5.0.0
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/README.md +249 -4
- package/package.json +3 -2
- package/src/index.d.ts +16 -0
- package/src/index.js +16 -0
- package/src/index.js.map +1 -1
- package/src/lib/aggregate-root/aggregate-root-config.d.ts +37 -0
- package/src/lib/aggregate-root/aggregate-root-config.js +25 -0
- package/src/lib/aggregate-root/aggregate-root-config.js.map +1 -0
- package/src/lib/aggregate-root/aggregate-root-name.d.ts +1 -1
- package/src/lib/aggregate-root/aggregate-root-name.js +1 -5
- package/src/lib/aggregate-root/aggregate-root-name.js.map +1 -1
- package/src/lib/aggregate-root/aggregate-root.d.ts +3 -5
- package/src/lib/aggregate-root/aggregate-root.js +10 -9
- package/src/lib/aggregate-root/aggregate-root.js.map +1 -1
- package/src/lib/aggregate-root/apply-event.decorator.d.ts +1 -1
- package/src/lib/aggregate-root/apply-event.decorator.js.map +1 -1
- package/src/lib/aggregate-root/reflection.d.ts +1 -1
- package/src/lib/aggregate-root/snapshot-aware.d.ts +47 -0
- package/src/lib/aggregate-root/snapshot-aware.js +52 -0
- package/src/lib/aggregate-root/snapshot-aware.js.map +1 -0
- package/src/lib/domain-event-registrations.d.ts +1 -1
- package/src/lib/domain-event-registrations.js.map +1 -1
- package/src/lib/domain-event-subscription.d.ts +1 -1
- package/src/lib/domain-event-subscription.js.map +1 -1
- package/src/lib/exceptions/aggregate-class-not-snapshot-aware-exception.d.ts +6 -0
- package/src/lib/exceptions/aggregate-class-not-snapshot-aware-exception.js +13 -0
- package/src/lib/exceptions/aggregate-class-not-snapshot-aware-exception.js.map +1 -0
- package/src/lib/exceptions/aggregate-instance-not-snapshot-aware-exception.d.ts +6 -0
- package/src/lib/exceptions/aggregate-instance-not-snapshot-aware-exception.js +13 -0
- package/src/lib/exceptions/aggregate-instance-not-snapshot-aware-exception.js.map +1 -0
- package/src/lib/exceptions/snapshot-revision-mismatch-exception.d.ts +6 -0
- package/src/lib/exceptions/snapshot-revision-mismatch-exception.js +14 -0
- package/src/lib/exceptions/snapshot-revision-mismatch-exception.js.map +1 -0
- package/src/lib/metadata-keys.d.ts +1 -0
- package/src/lib/metadata-keys.js +2 -1
- package/src/lib/metadata-keys.js.map +1 -1
- package/src/lib/snapshot-strategy/all-of-snapshot-strategy.d.ts +29 -0
- package/src/lib/snapshot-strategy/all-of-snapshot-strategy.js +40 -0
- package/src/lib/snapshot-strategy/all-of-snapshot-strategy.js.map +1 -0
- package/src/lib/snapshot-strategy/any-of-snapshot-strategy.d.ts +30 -0
- package/src/lib/snapshot-strategy/any-of-snapshot-strategy.js +41 -0
- package/src/lib/snapshot-strategy/any-of-snapshot-strategy.js.map +1 -0
- package/src/lib/snapshot-strategy/for-aggregate-roots-strategy.d.ts +30 -0
- package/src/lib/snapshot-strategy/for-aggregate-roots-strategy.js +51 -0
- package/src/lib/snapshot-strategy/for-aggregate-roots-strategy.js.map +1 -0
- package/src/lib/snapshot-strategy/for-count-snapshot-strategy.d.ts +28 -0
- package/src/lib/snapshot-strategy/for-count-snapshot-strategy.js +41 -0
- package/src/lib/snapshot-strategy/for-count-snapshot-strategy.js.map +1 -0
- package/src/lib/snapshot-strategy/for-events-snapshot-strategy.d.ts +33 -0
- package/src/lib/snapshot-strategy/for-events-snapshot-strategy.js +37 -0
- package/src/lib/snapshot-strategy/for-events-snapshot-strategy.js.map +1 -0
- package/src/lib/snapshot-strategy/no-snapshot-strategy.d.ts +18 -0
- package/src/lib/snapshot-strategy/no-snapshot-strategy.js +25 -0
- package/src/lib/snapshot-strategy/no-snapshot-strategy.js.map +1 -0
- package/src/lib/snapshot-strategy/snapshot-strategy.d.ts +26 -0
- package/src/lib/snapshot-strategy/snapshot-strategy.js +20 -0
- package/src/lib/snapshot-strategy/snapshot-strategy.js.map +1 -0
- package/src/lib/storage/abstract-event-store.d.ts +8 -2
- package/src/lib/storage/abstract-event-store.js +8 -3
- package/src/lib/storage/abstract-event-store.js.map +1 -1
- package/src/lib/storage/event-store.d.ts +29 -0
- package/src/lib/storage/event-store.js.map +1 -1
- package/src/lib/storage/snapshot/abstract-snapshot-store.d.ts +14 -0
- package/src/lib/storage/snapshot/abstract-snapshot-store.js +26 -0
- package/src/lib/storage/snapshot/abstract-snapshot-store.js.map +1 -0
- package/src/lib/storage/snapshot/no-op-snapshot-store.d.ts +11 -0
- package/src/lib/storage/snapshot/no-op-snapshot-store.js +25 -0
- package/src/lib/storage/snapshot/no-op-snapshot-store.js.map +1 -0
- package/src/lib/storage/snapshot/snapshot-store.d.ts +38 -0
- package/src/lib/storage/snapshot/snapshot-store.js +5 -0
- package/src/lib/storage/snapshot/snapshot-store.js.map +1 -0
- package/src/lib/storage/snapshot/stored-snapshot.d.ts +17 -0
- package/src/lib/storage/snapshot/stored-snapshot.js +36 -0
- package/src/lib/storage/snapshot/stored-snapshot.js.map +1 -0
- package/src/lib/storage/stored-event.d.ts +1 -1
- package/src/lib/storage/stored-event.js.map +1 -1
- package/src/lib/utils/type-utils.d.ts +4 -0
- package/src/lib/utils/type-utils.js.map +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,11 @@ What Event Nest is Not:
|
|
|
25
25
|
- [Concepts](#concepts)
|
|
26
26
|
- [Event](#event)
|
|
27
27
|
- [Aggregate Root](#aggregate-root)
|
|
28
|
+
- [Snapshots](#snapshots)
|
|
29
|
+
- [Making an aggregate root snapshot-aware](#making-an-aggregate-root-snapshot-aware)
|
|
30
|
+
- [Snapshot strategies](#snapshot-strategies)
|
|
31
|
+
- [Loading an aggregate root with a snapshot](#loading-an-aggregate-root-with-a-snapshot)
|
|
32
|
+
- [Snapshot revision](#snapshot-revision)
|
|
28
33
|
- [Domain Event Subscription](#domain-event-subscription)
|
|
29
34
|
- [Order of execution in subscriptions](#order-of-execution-in-subscriptions)
|
|
30
35
|
- [Waiting for subscriptions to complete](#waiting-for-subscriptions-to-complete)
|
|
@@ -71,6 +76,26 @@ export class AppModule {}
|
|
|
71
76
|
```
|
|
72
77
|
The collections specified in the configuration will store the aggregates and events.
|
|
73
78
|
|
|
79
|
+
If you want to enable [snapshots](#snapshots), you will also need to provide a `snapshotCollection` and a `snapshotStrategy` :
|
|
80
|
+
```typescript
|
|
81
|
+
import { ForCountSnapshotStrategy } from "@event-nest/core";
|
|
82
|
+
import { EventNestMongoDbModule } from "@event-nest/mongodb";
|
|
83
|
+
import { Module } from "@nestjs/common";
|
|
84
|
+
|
|
85
|
+
@Module({
|
|
86
|
+
imports: [
|
|
87
|
+
EventNestMongoDbModule.forRoot({
|
|
88
|
+
connectionUri: "mongodb://localhost:27017/example",
|
|
89
|
+
aggregatesCollection: "aggregates-collection",
|
|
90
|
+
eventsCollection: "events-collection",
|
|
91
|
+
snapshotCollection: "snapshots-collection",
|
|
92
|
+
snapshotStrategy: new ForCountSnapshotStrategy({ count: 10 })
|
|
93
|
+
}),
|
|
94
|
+
],
|
|
95
|
+
})
|
|
96
|
+
export class AppModule {}
|
|
97
|
+
```
|
|
98
|
+
|
|
74
99
|
|
|
75
100
|
### PostgreSQL setup
|
|
76
101
|
|
|
@@ -99,6 +124,28 @@ export class AppModule {}
|
|
|
99
124
|
|
|
100
125
|
If the database user has privileges to create tables, set the `ensureTablesExist` option to true to automatically create the necessary tables during bootstrap. Otherwise, refer to the manual table creation instructions below.
|
|
101
126
|
|
|
127
|
+
If you want to enable [snapshots](#snapshots), you will also need to provide a `snapshotTableName` and a `snapshotStrategy` :
|
|
128
|
+
```typescript
|
|
129
|
+
import { ForCountSnapshotStrategy } from "@event-nest/core";
|
|
130
|
+
import { EventNestPostgreSQLModule } from "@event-nest/postgresql";
|
|
131
|
+
import { Module } from "@nestjs/common";
|
|
132
|
+
|
|
133
|
+
@Module({
|
|
134
|
+
imports: [
|
|
135
|
+
EventNestPostgreSQLModule.forRoot({
|
|
136
|
+
aggregatesTableName: "aggregates",
|
|
137
|
+
connectionUri: "postgresql://postgres:password@localhost:5432/event_nest",
|
|
138
|
+
eventsTableName: "events",
|
|
139
|
+
schemaName: "event_nest_schema",
|
|
140
|
+
ensureTablesExist: true,
|
|
141
|
+
snapshotTableName: "snapshots",
|
|
142
|
+
snapshotStrategy: new ForCountSnapshotStrategy({ count: 10 })
|
|
143
|
+
})
|
|
144
|
+
]
|
|
145
|
+
})
|
|
146
|
+
export class AppModule {}
|
|
147
|
+
```
|
|
148
|
+
|
|
102
149
|
|
|
103
150
|
#### Manual creation of PostgreSQL tables
|
|
104
151
|
If you prefer to create the tables manually, the following guidelines describe the structure of the tables that need to be created.
|
|
@@ -122,6 +169,16 @@ If you prefer to create the tables manually, the following guidelines describe t
|
|
|
122
169
|
| payload | jsonb | A JSON representation of the event's additional data. |
|
|
123
170
|
| created_at | timestamp with time zone | The timestamp when the event was produced. <br/>Must be set as NOT NULL |
|
|
124
171
|
|
|
172
|
+
**Snapshots Table (optional) :**
|
|
173
|
+
|
|
174
|
+
| Column Name | Type | Description |
|
|
175
|
+
|------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------|
|
|
176
|
+
| id | uuid | The unique identifier of the snapshot. <br/>Must be set as NOT NULL and it is the table's primary key |
|
|
177
|
+
| aggregate_root_id | uuid | The id of the aggregate that the snapshot belongs to.<br/> Must be set as NOT NULL and it is a foreign key to the aggregates table |
|
|
178
|
+
| aggregate_root_version | integer | The version of the aggregate root when the snapshot was created. <br/>Must be set as NOT NULL |
|
|
179
|
+
| payload | jsonb | A JSON representation of the snapshot data. <br/>Must be set as NOT NULL |
|
|
180
|
+
| revision | integer | The snapshot revision number. <br/>Must be set as NOT NULL |
|
|
181
|
+
|
|
125
182
|
|
|
126
183
|
## Concepts
|
|
127
184
|
### Event
|
|
@@ -180,9 +237,11 @@ export class UserUpdatedEvent {
|
|
|
180
237
|
|
|
181
238
|
Next, we will define the aggregate root for the user. Let's break down what this class should do and how.
|
|
182
239
|
|
|
183
|
-
First of all, the class has to extend the `AggregateRoot` class, and it has to be decorated with the `@
|
|
240
|
+
First of all, the class has to extend the `AggregateRoot` class, and it has to be decorated with the `@AggregateRootConfig` decorator.
|
|
184
241
|
The name is required to associate persisted events with the correct aggregate root when retrieving them from storage.
|
|
185
242
|
|
|
243
|
+
> **Note:** The `@AggregateRootName` decorator is deprecated and will be removed in version 7.x. Use `@AggregateRootConfig` instead.
|
|
244
|
+
|
|
186
245
|
Now let's talk about constructors. TypeScript doesn't allow us to define multiple constructors. Therefore, if we have two ways of creating an object, we could use static methods as factories.
|
|
187
246
|
In our case, we have the following creation cases :
|
|
188
247
|
* The user is new, and we need to create it from scratch. In that case, we create a new `UserCreatedEvent` event, and we `append` it to the aggregate root's event stream.
|
|
@@ -198,9 +257,9 @@ It's important to note that the append method will not save the event. All the a
|
|
|
198
257
|
|
|
199
258
|
|
|
200
259
|
```typescript
|
|
201
|
-
import { AggregateRoot,
|
|
260
|
+
import { AggregateRoot, AggregateRootConfig, ApplyEvent, StoredEvent } from "@event-nest/core";
|
|
202
261
|
|
|
203
|
-
@
|
|
262
|
+
@AggregateRootConfig({ name: "User" })
|
|
204
263
|
export class User extends AggregateRoot {
|
|
205
264
|
private name: string;
|
|
206
265
|
private email: string;
|
|
@@ -276,6 +335,192 @@ export class UserService {
|
|
|
276
335
|
}
|
|
277
336
|
```
|
|
278
337
|
|
|
338
|
+
### Snapshots
|
|
339
|
+
As the number of events for an aggregate root grows, replaying the full event stream to reconstruct its state can become increasingly slow. Snapshots address this by periodically capturing the aggregate's state, so that reconstitution can start from a recent snapshot instead of replaying every event from the beginning.
|
|
340
|
+
|
|
341
|
+
Snapshots are entirely optional. When enabled, the library will automatically create snapshots based on a configurable strategy and use them during reconstitution. The complete event history is always preserved in storage regardless of whether snapshots are used.
|
|
342
|
+
|
|
343
|
+
To use snapshots, you need to :
|
|
344
|
+
* Configure a snapshot strategy and a snapshot storage location in your module setup (see [MongoDB setup](#mongodb-setup) or [PostgreSQL setup](#postgresql-setup))
|
|
345
|
+
* Have your aggregate root classes implement the `SnapshotAware` interface
|
|
346
|
+
|
|
347
|
+
#### Making an aggregate root snapshot-aware
|
|
348
|
+
|
|
349
|
+
An aggregate root needs two things to support snapshots :
|
|
350
|
+
|
|
351
|
+
1. The `@AggregateRootConfig` decorator must include a `snapshotRevision` number.
|
|
352
|
+
2. The class must implement the `SnapshotAware` interface, which requires two methods: `toSnapshot()` and `applySnapshot()`.
|
|
353
|
+
|
|
354
|
+
Let's extend the `User` example from above to support snapshots :
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { AggregateRoot, AggregateRootConfig, ApplyEvent, SnapshotAware, StoredEvent } from "@event-nest/core";
|
|
358
|
+
|
|
359
|
+
interface UserSnapshot {
|
|
360
|
+
name: string;
|
|
361
|
+
email: string;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@AggregateRootConfig({ name: "User", snapshotRevision: 1 })
|
|
365
|
+
export class User extends AggregateRoot implements SnapshotAware<UserSnapshot> {
|
|
366
|
+
private name: string;
|
|
367
|
+
private email: string;
|
|
368
|
+
|
|
369
|
+
private constructor(id: string) {
|
|
370
|
+
super(id);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
public static createNew(id: string, name: string, email: string): User {
|
|
374
|
+
const user = new User(id);
|
|
375
|
+
const event = new UserCreatedEvent(name, email);
|
|
376
|
+
user.applyUserCreatedEvent(event);
|
|
377
|
+
user.append(event);
|
|
378
|
+
return user;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
public static fromEvents(id: string, events: Array<StoredEvent>, snapshot?: UserSnapshot): User {
|
|
382
|
+
const user = new User(id);
|
|
383
|
+
user.reconstitute(events, snapshot);
|
|
384
|
+
return user;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
toSnapshot(): UserSnapshot {
|
|
388
|
+
return { name: this.name, email: this.email };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
applySnapshot(snapshot: UserSnapshot) {
|
|
392
|
+
this.name = snapshot.name;
|
|
393
|
+
this.email = snapshot.email;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public update(newName: string) {
|
|
397
|
+
const event = new UserUpdatedEvent(newName);
|
|
398
|
+
this.applyUserUpdatedEvent(event);
|
|
399
|
+
this.append(event);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
@ApplyEvent(UserCreatedEvent)
|
|
403
|
+
private applyUserCreatedEvent(event: UserCreatedEvent) {
|
|
404
|
+
this.name = event.name;
|
|
405
|
+
this.email = event.email;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
@ApplyEvent(UserUpdatedEvent)
|
|
409
|
+
private applyUserUpdatedEvent(event: UserUpdatedEvent) {
|
|
410
|
+
this.name = event.newName;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
The `toSnapshot()` method returns a plain representation of the aggregate's current state. The `applySnapshot()` method restores that state when a snapshot is loaded from storage. The `reconstitute` method accepts an optional snapshot parameter. When a snapshot is provided, it will be applied first, and then any remaining events will be replayed on top of it.
|
|
416
|
+
|
|
417
|
+
Note that when calling `commit`, the library will automatically evaluate the configured snapshot strategy to determine whether a new snapshot should be created. If the strategy says yes, it will call `toSnapshot()` and persist the result. You don't need to manage snapshot creation manually.
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
#### Snapshot strategies
|
|
421
|
+
|
|
422
|
+
Snapshot strategies determine when the library should create a snapshot for an aggregate root. You configure the strategy once in your module setup, and it applies globally. Several built-in strategies are available, and they can be composed to build more complex rules.
|
|
423
|
+
|
|
424
|
+
**ForCountSnapshotStrategy**
|
|
425
|
+
|
|
426
|
+
Creates a snapshot when the aggregate root crosses a version threshold. For example, with a count of 10, a snapshot will be created when the version goes from 9 to 10, from 19 to 20, and so on.
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
import { ForCountSnapshotStrategy } from "@event-nest/core";
|
|
430
|
+
|
|
431
|
+
new ForCountSnapshotStrategy({ count: 10 })
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**ForEventsSnapshotStrategy**
|
|
435
|
+
|
|
436
|
+
Creates a snapshot when specific event types are present in the uncommitted events. This is useful when certain events represent significant state changes that are worth snapshotting.
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
import { ForEventsSnapshotStrategy } from "@event-nest/core";
|
|
440
|
+
|
|
441
|
+
new ForEventsSnapshotStrategy({ eventClasses: [UserCreatedEvent, UserUpdatedEvent] })
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**ForAggregateRootsStrategy**
|
|
445
|
+
|
|
446
|
+
Creates snapshots only for specific aggregate root classes. This is useful when only some of your aggregates have enough events to benefit from snapshots.
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import { ForAggregateRootsStrategy } from "@event-nest/core";
|
|
450
|
+
|
|
451
|
+
new ForAggregateRootsStrategy({ aggregates: [User, Order] })
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**AllOfSnapshotStrategy**
|
|
455
|
+
|
|
456
|
+
A composite strategy that creates a snapshot only when **all** of the provided strategies agree. This acts as a logical AND.
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
import { AllOfSnapshotStrategy, ForAggregateRootsStrategy, ForCountSnapshotStrategy } from "@event-nest/core";
|
|
460
|
+
|
|
461
|
+
new AllOfSnapshotStrategy([
|
|
462
|
+
new ForAggregateRootsStrategy({ aggregates: [User] }),
|
|
463
|
+
new ForCountSnapshotStrategy({ count: 10 })
|
|
464
|
+
])
|
|
465
|
+
```
|
|
466
|
+
In this example, snapshots will only be created for `User` aggregates and only when they cross a version threshold of 10.
|
|
467
|
+
|
|
468
|
+
**AnyOfSnapshotStrategy**
|
|
469
|
+
|
|
470
|
+
A composite strategy that creates a snapshot when **any** of the provided strategies agrees. This acts as a logical OR.
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
import { AnyOfSnapshotStrategy, ForCountSnapshotStrategy, ForEventsSnapshotStrategy } from "@event-nest/core";
|
|
474
|
+
|
|
475
|
+
new AnyOfSnapshotStrategy([
|
|
476
|
+
new ForCountSnapshotStrategy({ count: 10 }),
|
|
477
|
+
new ForEventsSnapshotStrategy({ eventClasses: [UserCreatedEvent] })
|
|
478
|
+
])
|
|
479
|
+
```
|
|
480
|
+
In this example, a snapshot will be created either when the version crosses a threshold of 10 or when a `UserCreatedEvent` is committed.
|
|
481
|
+
|
|
482
|
+
The composite strategies can be nested to express more complex rules. For example, you could use an `AnyOfSnapshotStrategy` that contains an `AllOfSnapshotStrategy` alongside a `ForEventsSnapshotStrategy`.
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
#### Loading an aggregate root with a snapshot
|
|
486
|
+
|
|
487
|
+
The `EventStore` provides a `findWithSnapshot` method that retrieves the latest snapshot for an aggregate root along with any events that occurred after that snapshot. If no snapshot is found, all events are returned.
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
import { EVENT_STORE, EventStore } from "@event-nest/core";
|
|
491
|
+
|
|
492
|
+
@Injectable()
|
|
493
|
+
export class UserService {
|
|
494
|
+
constructor(@Inject(EVENT_STORE) private eventStore: EventStore) {}
|
|
495
|
+
|
|
496
|
+
async updateUser(id: string, newName: string) {
|
|
497
|
+
const { events, snapshot } = await this.eventStore.findWithSnapshot(User, id);
|
|
498
|
+
const user = User.fromEvents(id, events, snapshot);
|
|
499
|
+
const userWithPublisher = this.eventStore.addPublisher(user);
|
|
500
|
+
user.update(newName);
|
|
501
|
+
await userWithPublisher.commit();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
If the snapshot cannot be loaded for any reason (for example, a revision mismatch), you can fall back to loading all events with `findByAggregateRootId` as shown in the [Aggregate Root](#aggregate-root) example.
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
#### Snapshot revision
|
|
510
|
+
|
|
511
|
+
The `snapshotRevision` number in `@AggregateRootConfig` is a compatibility version for the snapshot format. When loading a snapshot from storage, the library compares the stored revision with the one defined on the class. If they don't match, a `SnapshotRevisionMismatchException` is thrown.
|
|
512
|
+
|
|
513
|
+
This mechanism exists to protect against applying outdated snapshots when the structure of your snapshot changes. For example, if you add a new field to a `User` aggregate and update `toSnapshot()` to include it, the old snapshots in the database no longer match the new format. By incrementing the `snapshotRevision`, the library will reject old snapshots and the aggregate will be reconstituted from the full event stream instead. New snapshots created from that point on will use the updated format and revision number.
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
// Before: snapshot only includes name and email
|
|
517
|
+
@AggregateRootConfig({ name: "User", snapshotRevision: 1 })
|
|
518
|
+
|
|
519
|
+
// After: snapshot now includes name, email, and role
|
|
520
|
+
@AggregateRootConfig({ name: "User", snapshotRevision: 2 })
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
|
|
279
524
|
### Domain Event Subscription
|
|
280
525
|
When working with event sourcing, you will often need to update other parts of your system after an event has been persisted. For example, you may have a read model for users that needs to be updated when a user is created or updated. Or, perhaps you need to send an email notification when a specific event occurs.
|
|
281
526
|
|
|
@@ -338,7 +583,7 @@ The `DomainEventSubscription` decorator supports an alternative syntax for those
|
|
|
338
583
|
When your subscription is defined like this, the `commit` method will not return until the `onDomainEvent` method is completed for all the events that the service is subscribed to.
|
|
339
584
|
|
|
340
585
|
If your subscription throws an exception, the exception will be wrapped in a `SubscriptionException` which will be thrown by the `commit` method.
|
|
341
|
-
|
|
586
|
+
> **Note:** When the `commit` method throws such a `SubscriptionException`, it doesn't mean that the events were not saved to the storage. Since the subscriptions run after the events are saved, an exception from a subscription doesn't roll back the events.
|
|
342
587
|
|
|
343
588
|
|
|
344
589
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event-nest/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Nick Tsitlakidis",
|
|
6
6
|
"description": "Event sourcing module for NestJS. It provides a set of decorators and classes to build an application based on event sourcing.",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "commonjs",
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@nestjs/common": "
|
|
25
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
26
26
|
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
|
27
27
|
"reflect-metadata": "^0.1.12 || ^0.2.0",
|
|
28
28
|
"rxjs": "^7.2.0",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"class-transformer": "^0.5.1",
|
|
33
33
|
"es-toolkit": "^1.32.0"
|
|
34
34
|
},
|
|
35
|
+
"packageManager": "pnpm@10.29.2",
|
|
35
36
|
"types": "./src/index.d.ts",
|
|
36
37
|
"main": "./src/index.js"
|
|
37
38
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
export * from "./lib/aggregate-root/aggregate-root";
|
|
2
|
+
export * from "./lib/aggregate-root/aggregate-root-config";
|
|
2
3
|
export * from "./lib/aggregate-root/aggregate-root-name";
|
|
3
4
|
export * from "./lib/aggregate-root/apply-event.decorator";
|
|
5
|
+
export * from "./lib/aggregate-root/snapshot-aware";
|
|
4
6
|
export * from "./lib/core-module-options";
|
|
5
7
|
export * from "./lib/domain-event";
|
|
6
8
|
export * from "./lib/domain-event-emitter";
|
|
7
9
|
export * from "./lib/domain-event-subscription";
|
|
10
|
+
export * from "./lib/exceptions/aggregate-class-not-snapshot-aware-exception";
|
|
11
|
+
export * from "./lib/exceptions/aggregate-instance-not-snapshot-aware-exception";
|
|
8
12
|
export * from "./lib/exceptions/event-concurrency-exception";
|
|
9
13
|
export * from "./lib/exceptions/event-name-conflict-exception";
|
|
10
14
|
export * from "./lib/exceptions/missing-aggregate-root-name-exception";
|
|
15
|
+
export * from "./lib/exceptions/snapshot-revision-mismatch-exception";
|
|
11
16
|
export * from "./lib/exceptions/subscription-exception";
|
|
12
17
|
export * from "./lib/exceptions/unknown-event-exception";
|
|
13
18
|
export * from "./lib/on-domain-event";
|
|
14
19
|
export * from "./lib/published-domain-event";
|
|
20
|
+
export * from "./lib/snapshot-strategy/all-of-snapshot-strategy";
|
|
21
|
+
export * from "./lib/snapshot-strategy/any-of-snapshot-strategy";
|
|
22
|
+
export * from "./lib/snapshot-strategy/for-aggregate-roots-strategy";
|
|
23
|
+
export * from "./lib/snapshot-strategy/for-count-snapshot-strategy";
|
|
24
|
+
export * from "./lib/snapshot-strategy/for-events-snapshot-strategy";
|
|
25
|
+
export * from "./lib/snapshot-strategy/no-snapshot-strategy";
|
|
26
|
+
export * from "./lib/snapshot-strategy/snapshot-strategy";
|
|
15
27
|
export * from "./lib/storage/abstract-event-store";
|
|
16
28
|
export * from "./lib/storage/event-store";
|
|
29
|
+
export * from "./lib/storage/snapshot/abstract-snapshot-store";
|
|
30
|
+
export * from "./lib/storage/snapshot/no-op-snapshot-store";
|
|
31
|
+
export * from "./lib/storage/snapshot/snapshot-store";
|
|
32
|
+
export * from "./lib/storage/snapshot/stored-snapshot";
|
|
17
33
|
export * from "./lib/storage/stored-aggregate-root";
|
|
18
34
|
export * from "./lib/storage/stored-event";
|
|
19
35
|
export * from "./lib/utils/type-utils";
|
package/src/index.js
CHANGED
|
@@ -2,21 +2,37 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
tslib_1.__exportStar(require("./lib/aggregate-root/aggregate-root"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./lib/aggregate-root/aggregate-root-config"), exports);
|
|
5
6
|
tslib_1.__exportStar(require("./lib/aggregate-root/aggregate-root-name"), exports);
|
|
6
7
|
tslib_1.__exportStar(require("./lib/aggregate-root/apply-event.decorator"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./lib/aggregate-root/snapshot-aware"), exports);
|
|
7
9
|
tslib_1.__exportStar(require("./lib/core-module-options"), exports);
|
|
8
10
|
tslib_1.__exportStar(require("./lib/domain-event"), exports);
|
|
9
11
|
tslib_1.__exportStar(require("./lib/domain-event-emitter"), exports);
|
|
10
12
|
tslib_1.__exportStar(require("./lib/domain-event-subscription"), exports);
|
|
13
|
+
tslib_1.__exportStar(require("./lib/exceptions/aggregate-class-not-snapshot-aware-exception"), exports);
|
|
14
|
+
tslib_1.__exportStar(require("./lib/exceptions/aggregate-instance-not-snapshot-aware-exception"), exports);
|
|
11
15
|
tslib_1.__exportStar(require("./lib/exceptions/event-concurrency-exception"), exports);
|
|
12
16
|
tslib_1.__exportStar(require("./lib/exceptions/event-name-conflict-exception"), exports);
|
|
13
17
|
tslib_1.__exportStar(require("./lib/exceptions/missing-aggregate-root-name-exception"), exports);
|
|
18
|
+
tslib_1.__exportStar(require("./lib/exceptions/snapshot-revision-mismatch-exception"), exports);
|
|
14
19
|
tslib_1.__exportStar(require("./lib/exceptions/subscription-exception"), exports);
|
|
15
20
|
tslib_1.__exportStar(require("./lib/exceptions/unknown-event-exception"), exports);
|
|
16
21
|
tslib_1.__exportStar(require("./lib/on-domain-event"), exports);
|
|
17
22
|
tslib_1.__exportStar(require("./lib/published-domain-event"), exports);
|
|
23
|
+
tslib_1.__exportStar(require("./lib/snapshot-strategy/all-of-snapshot-strategy"), exports);
|
|
24
|
+
tslib_1.__exportStar(require("./lib/snapshot-strategy/any-of-snapshot-strategy"), exports);
|
|
25
|
+
tslib_1.__exportStar(require("./lib/snapshot-strategy/for-aggregate-roots-strategy"), exports);
|
|
26
|
+
tslib_1.__exportStar(require("./lib/snapshot-strategy/for-count-snapshot-strategy"), exports);
|
|
27
|
+
tslib_1.__exportStar(require("./lib/snapshot-strategy/for-events-snapshot-strategy"), exports);
|
|
28
|
+
tslib_1.__exportStar(require("./lib/snapshot-strategy/no-snapshot-strategy"), exports);
|
|
29
|
+
tslib_1.__exportStar(require("./lib/snapshot-strategy/snapshot-strategy"), exports);
|
|
18
30
|
tslib_1.__exportStar(require("./lib/storage/abstract-event-store"), exports);
|
|
19
31
|
tslib_1.__exportStar(require("./lib/storage/event-store"), exports);
|
|
32
|
+
tslib_1.__exportStar(require("./lib/storage/snapshot/abstract-snapshot-store"), exports);
|
|
33
|
+
tslib_1.__exportStar(require("./lib/storage/snapshot/no-op-snapshot-store"), exports);
|
|
34
|
+
tslib_1.__exportStar(require("./lib/storage/snapshot/snapshot-store"), exports);
|
|
35
|
+
tslib_1.__exportStar(require("./lib/storage/snapshot/stored-snapshot"), exports);
|
|
20
36
|
tslib_1.__exportStar(require("./lib/storage/stored-aggregate-root"), exports);
|
|
21
37
|
tslib_1.__exportStar(require("./lib/storage/stored-event"), exports);
|
|
22
38
|
tslib_1.__exportStar(require("./lib/utils/type-utils"), exports);
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/core/src/index.ts"],"names":[],"mappings":";;;AAAA,8EAAoD;AACpD,mFAAyD;AACzD,qFAA2D;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/core/src/index.ts"],"names":[],"mappings":";;;AAAA,8EAAoD;AACpD,qFAA2D;AAC3D,mFAAyD;AACzD,qFAA2D;AAC3D,8EAAoD;AAEpD,oEAA0C;AAC1C,6DAAmC;AACnC,qEAA2C;AAC3C,0EAAgD;AAEhD,wGAA8E;AAC9E,2GAAiF;AACjF,uFAA6D;AAC7D,yFAA+D;AAC/D,iGAAuE;AACvE,gGAAsE;AACtE,kFAAwD;AACxD,mFAAyD;AAEzD,gEAAsC;AAEtC,uEAA6C;AAE7C,2FAAiE;AACjE,2FAAiE;AACjE,+FAAqE;AACrE,8FAAoE;AACpE,+FAAqE;AACrE,uFAA6D;AAC7D,oFAA0D;AAE1D,6EAAmD;AACnD,oEAA0C;AAC1C,yFAA+D;AAC/D,sFAA4D;AAC5D,gFAAsD;AACtD,iFAAuD;AACvD,8EAAoD;AACpD,qEAA2C;AAE3C,iEAAuC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AggregateRootClass, SnapshotAwareAggregateRoot } from "../storage/event-store";
|
|
2
|
+
export interface AggregateRootConfigParameters {
|
|
3
|
+
/**
|
|
4
|
+
* Will be saved with each event in the database and used during event retrieval to ensure
|
|
5
|
+
* the correct events are retrieved
|
|
6
|
+
*/
|
|
7
|
+
name: string;
|
|
8
|
+
/**
|
|
9
|
+
* Optinal snapshot revision number that's used for snapshot optimization.
|
|
10
|
+
*/
|
|
11
|
+
snapshotRevision?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A decorator to configure an aggregate root class with a unique name and optional snapshot revision.
|
|
15
|
+
*
|
|
16
|
+
* @param params - Configuration parameters including the aggregate root name and optional snapshot revision
|
|
17
|
+
*/
|
|
18
|
+
export declare const AggregateRootConfig: ({ name, snapshotRevision }: AggregateRootConfigParameters) => ClassDecorator;
|
|
19
|
+
/**
|
|
20
|
+
* Used to obtain the name attached to AggregateRoot class by getting it's metadata
|
|
21
|
+
* Works with both @see {@link AggregateRootConfig} and @see {@link AggregateRootName}
|
|
22
|
+
* Uses function overloading, because it's already known that SnapshotAwareAggregateRoot has name defined
|
|
23
|
+
*
|
|
24
|
+
* @param targetClass - the AggregateRoot class
|
|
25
|
+
* @returns snapshotRevision from provided {@link AggregateRootConfigParameters} or undefined if not present
|
|
26
|
+
*/
|
|
27
|
+
export declare function getAggregateRootName(targetClass: AggregateRootClass<SnapshotAwareAggregateRoot>): string;
|
|
28
|
+
export declare function getAggregateRootName(targetClass: AggregateRootClass<unknown>): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Used to obtain the snapshotRevision number attached to AggregateRoot class by getting it's metadata
|
|
31
|
+
* Uses function overloading, because it's already known that SnapshotAwareAggregateRoot has snasphotRevision defined
|
|
32
|
+
*
|
|
33
|
+
* @param aggregateRootClass - the AggregateRootClass
|
|
34
|
+
* @returns snapshotRevision from provided {@link AggregateRootConfigParameters} or undefined if not present
|
|
35
|
+
*/
|
|
36
|
+
export declare function getAggregateRootSnapshotRevision(targetClass: AggregateRootClass<SnapshotAwareAggregateRoot>): number;
|
|
37
|
+
export declare function getAggregateRootSnapshotRevision(targetClass: AggregateRootClass<unknown>): number | undefined;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AggregateRootConfig = void 0;
|
|
4
|
+
exports.getAggregateRootName = getAggregateRootName;
|
|
5
|
+
exports.getAggregateRootSnapshotRevision = getAggregateRootSnapshotRevision;
|
|
6
|
+
const metadata_keys_1 = require("../metadata-keys");
|
|
7
|
+
/**
|
|
8
|
+
* A decorator to configure an aggregate root class with a unique name and optional snapshot revision.
|
|
9
|
+
*
|
|
10
|
+
* @param params - Configuration parameters including the aggregate root name and optional snapshot revision
|
|
11
|
+
*/
|
|
12
|
+
const AggregateRootConfig = ({ name, snapshotRevision }) => {
|
|
13
|
+
return (target) => {
|
|
14
|
+
Reflect.defineMetadata(metadata_keys_1.AGGREGATE_ROOT_NAME_KEY, { aggregateRootName: name }, target);
|
|
15
|
+
Reflect.defineMetadata(metadata_keys_1.AGGREGATE_ROOT_SNAPSHOT_REVISION_KEY, { aggregateRootSnapshotRevision: snapshotRevision }, target);
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
exports.AggregateRootConfig = AggregateRootConfig;
|
|
19
|
+
function getAggregateRootName(targetClass) {
|
|
20
|
+
return Reflect.getMetadata(metadata_keys_1.AGGREGATE_ROOT_NAME_KEY, targetClass)?.aggregateRootName;
|
|
21
|
+
}
|
|
22
|
+
function getAggregateRootSnapshotRevision(targetClass) {
|
|
23
|
+
return Reflect.getMetadata(metadata_keys_1.AGGREGATE_ROOT_SNAPSHOT_REVISION_KEY, targetClass)?.aggregateRootSnapshotRevision;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=aggregate-root-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate-root-config.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/aggregate-root/aggregate-root-config.ts"],"names":[],"mappings":";;;AAyCA,oDAEC;AAWD,4EAEC;AAxDD,oDAAiG;AAejG;;;;GAIG;AACI,MAAM,mBAAmB,GAAG,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAiC,EAAkB,EAAE;IAC7G,OAAO,CAAC,MAAc,EAAE,EAAE;QACtB,OAAO,CAAC,cAAc,CAAC,uCAAuB,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;QACrF,OAAO,CAAC,cAAc,CAClB,oDAAoC,EACpC,EAAE,6BAA6B,EAAE,gBAAgB,EAAE,EACnD,MAAM,CACT,CAAC;IACN,CAAC,CAAC;AACN,CAAC,CAAC;AATW,QAAA,mBAAmB,uBAS9B;AAYF,SAAgB,oBAAoB,CAAC,WAAwC;IACzE,OAAO,OAAO,CAAC,WAAW,CAAC,uCAAuB,EAAE,WAAW,CAAC,EAAE,iBAAiB,CAAC;AACxF,CAAC;AAWD,SAAgB,gCAAgC,CAAC,WAAwC;IACrF,OAAO,OAAO,CAAC,WAAW,CAAC,oDAAoC,EAAE,WAAW,CAAC,EAAE,6BAA6B,CAAC;AACjH,CAAC"}
|
|
@@ -5,8 +5,8 @@ import "reflect-metadata";
|
|
|
5
5
|
*
|
|
6
6
|
* It will also be used during the retrieval process of the events to make sure that
|
|
7
7
|
* the correct events are retrieved.
|
|
8
|
+
* @deprecated Use {@link AggregateRootConfig} decorator instead. It will be removed in version 7.x
|
|
8
9
|
* @param name The name of the aggregate root
|
|
9
10
|
* @constructor
|
|
10
11
|
*/
|
|
11
12
|
export declare const AggregateRootName: (name: string) => ClassDecorator;
|
|
12
|
-
export declare function getAggregateRootName(targetClass: Function): string | undefined;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AggregateRootName = void 0;
|
|
4
|
-
exports.getAggregateRootName = getAggregateRootName;
|
|
5
4
|
require("reflect-metadata");
|
|
6
5
|
const metadata_keys_1 = require("../metadata-keys");
|
|
7
6
|
/**
|
|
@@ -10,6 +9,7 @@ const metadata_keys_1 = require("../metadata-keys");
|
|
|
10
9
|
*
|
|
11
10
|
* It will also be used during the retrieval process of the events to make sure that
|
|
12
11
|
* the correct events are retrieved.
|
|
12
|
+
* @deprecated Use {@link AggregateRootConfig} decorator instead. It will be removed in version 7.x
|
|
13
13
|
* @param name The name of the aggregate root
|
|
14
14
|
* @constructor
|
|
15
15
|
*/
|
|
@@ -19,8 +19,4 @@ const AggregateRootName = (name) => {
|
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
21
|
exports.AggregateRootName = AggregateRootName;
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
23
|
-
function getAggregateRootName(targetClass) {
|
|
24
|
-
return Reflect.getMetadata(metadata_keys_1.AGGREGATE_ROOT_NAME_KEY, targetClass)?.aggregateRootName;
|
|
25
|
-
}
|
|
26
22
|
//# sourceMappingURL=aggregate-root-name.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate-root-name.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/aggregate-root/aggregate-root-name.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"aggregate-root-name.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/aggregate-root/aggregate-root-name.ts"],"names":[],"mappings":";;;AAAA,4BAA0B;AAE1B,oDAA2D;AAE3D;;;;;;;;;GASG;AACI,MAAM,iBAAiB,GAAG,CAAC,IAAY,EAAkB,EAAE;IAC9D,OAAO,CAAC,MAAc,EAAE,EAAE;QACtB,OAAO,CAAC,cAAc,CAAC,uCAAuB,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;IACzF,CAAC,CAAC;AACN,CAAC,CAAC;AAJW,QAAA,iBAAiB,qBAI5B"}
|
|
@@ -7,10 +7,6 @@ export declare abstract class AggregateRoot {
|
|
|
7
7
|
private _uncommittedEvents;
|
|
8
8
|
private _version;
|
|
9
9
|
protected constructor(_id: string, logger?: Logger);
|
|
10
|
-
/**
|
|
11
|
-
* @deprecated Use {@link uncommittedEvents} instead. It will be removed in version 5.x
|
|
12
|
-
*/
|
|
13
|
-
get appendedEvents(): Array<AggregateRootEvent<object>>;
|
|
14
10
|
get id(): string;
|
|
15
11
|
get logger(): Logger;
|
|
16
12
|
/**
|
|
@@ -51,9 +47,11 @@ export declare abstract class AggregateRoot {
|
|
|
51
47
|
* method will trigger all the matching {@link ApplyEvent} functions of the entity to populate the object based on
|
|
52
48
|
* application logic.
|
|
53
49
|
* @param events The events that will be sent to {@link ApplyEvent} functions
|
|
50
|
+
* @param snapshot Optional snapshot to apply before replaying events
|
|
51
|
+
* @throws AggregateInstanceNotSnapshotAwareException if a snapshot is provided but the aggregate is not snapshot-aware
|
|
54
52
|
* @throws UnknownEventException if an event is not known
|
|
55
53
|
*/
|
|
56
|
-
reconstitute(events: Array<StoredEvent
|
|
54
|
+
reconstitute<Snapshot = unknown>(events: Array<StoredEvent>, snapshot?: Snapshot): void;
|
|
57
55
|
resolveVersion(events: Array<StoredEvent>): void;
|
|
58
56
|
protected sortEvents(events: Array<StoredEvent>): Array<StoredEvent>;
|
|
59
57
|
private splitEvents;
|
|
@@ -8,6 +8,7 @@ const subscription_exception_1 = require("../exceptions/subscription-exception")
|
|
|
8
8
|
const unknown_event_exception_1 = require("../exceptions/unknown-event-exception");
|
|
9
9
|
const unregistered_event_exception_1 = require("../exceptions/unregistered-event-exception");
|
|
10
10
|
const reflection_1 = require("./reflection");
|
|
11
|
+
const snapshot_aware_1 = require("./snapshot-aware");
|
|
11
12
|
class AggregateRoot {
|
|
12
13
|
constructor(_id, logger) {
|
|
13
14
|
this._id = _id;
|
|
@@ -15,12 +16,6 @@ class AggregateRoot {
|
|
|
15
16
|
this._version = 0;
|
|
16
17
|
this._logger = (0, es_toolkit_1.isNil)(logger) ? new common_1.Logger(AggregateRoot.name) : logger;
|
|
17
18
|
}
|
|
18
|
-
/**
|
|
19
|
-
* @deprecated Use {@link uncommittedEvents} instead. It will be removed in version 5.x
|
|
20
|
-
*/
|
|
21
|
-
get appendedEvents() {
|
|
22
|
-
return [...this._uncommittedEvents];
|
|
23
|
-
}
|
|
24
19
|
get id() {
|
|
25
20
|
return this._id;
|
|
26
21
|
}
|
|
@@ -98,10 +93,16 @@ class AggregateRoot {
|
|
|
98
93
|
* method will trigger all the matching {@link ApplyEvent} functions of the entity to populate the object based on
|
|
99
94
|
* application logic.
|
|
100
95
|
* @param events The events that will be sent to {@link ApplyEvent} functions
|
|
96
|
+
* @param snapshot Optional snapshot to apply before replaying events
|
|
97
|
+
* @throws AggregateInstanceNotSnapshotAwareException if a snapshot is provided but the aggregate is not snapshot-aware
|
|
101
98
|
* @throws UnknownEventException if an event is not known
|
|
102
99
|
*/
|
|
103
|
-
reconstitute(events) {
|
|
100
|
+
reconstitute(events, snapshot) {
|
|
104
101
|
const startedAt = Date.now();
|
|
102
|
+
if (!(0, es_toolkit_1.isNil)(snapshot)) {
|
|
103
|
+
(0, snapshot_aware_1.assertIsSnapshotAwareAggregateRoot)(this);
|
|
104
|
+
this.applySnapshot(snapshot);
|
|
105
|
+
}
|
|
105
106
|
if (events.length > 0) {
|
|
106
107
|
const [unregistered, missingProcessor, known] = this.splitEvents(this.sortEvents(events));
|
|
107
108
|
if (unregistered.length > 0 || missingProcessor.length > 0) {
|
|
@@ -124,12 +125,12 @@ class AggregateRoot {
|
|
|
124
125
|
this._logger.debug(`Reconstitution of ${this.constructor.name} took ${duration}ms`);
|
|
125
126
|
}
|
|
126
127
|
resolveVersion(events) {
|
|
127
|
-
const sorted = events.
|
|
128
|
+
const sorted = events.toSorted((event1, event2) => event1.aggregateRootVersion - event2.aggregateRootVersion);
|
|
128
129
|
const lastElement = sorted.at(-1);
|
|
129
130
|
this._version = (0, es_toolkit_1.isNil)(lastElement) ? 0 : lastElement.aggregateRootVersion;
|
|
130
131
|
}
|
|
131
132
|
sortEvents(events) {
|
|
132
|
-
return events.
|
|
133
|
+
return events.toSorted((event1, event2) => event1.aggregateRootVersion - event2.aggregateRootVersion);
|
|
133
134
|
}
|
|
134
135
|
splitEvents(events) {
|
|
135
136
|
const known = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate-root.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/aggregate-root/aggregate-root.ts"],"names":[],"mappings":";;;AAAA,2CAAwC;AACxC,2CAAyC;AAEzC,8EAA4E;AAC5E,iFAA6E;AAC7E,mFAA8E;AAC9E,6FAAwF;AAGxF,6CAAuD;
|
|
1
|
+
{"version":3,"file":"aggregate-root.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/aggregate-root/aggregate-root.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
-
import { Class } from "type-
|
|
2
|
+
import { Class } from "../utils/type-utils";
|
|
3
3
|
/**
|
|
4
4
|
* A decorator to mark that a method is used to apply a specific event to an aggregate root.
|
|
5
5
|
* When an aggregate root has to be reconstituted based on persisted events, these methods
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply-event.decorator.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/aggregate-root/apply-event.decorator.ts"],"names":[],"mappings":";;AAgBA,gCAWC;AA3BD,4BAA0B;AAC1B,2CAAmC;
|
|
1
|
+
{"version":3,"file":"apply-event.decorator.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/aggregate-root/apply-event.decorator.ts"],"names":[],"mappings":";;AAgBA,gCAWC;AA3BD,4BAA0B;AAC1B,2CAAmC;AAEnC,+FAAyF;AACzF,oDAA6D;AAG7D;;;;;;;;GAQG;AACH,SAAgB,UAAU,CAAC,UAA0B;IACjD,IAAI,IAAA,kBAAK,EAAC,UAAU,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,0DAA0B,EAAE,CAAC;IAC3C,CAAC;IACD,OAAO,CAAC,cAAc,EAAE,WAAW,EAAE,EAAE;QACnC,OAAO,CAAC,cAAc,CAClB,yCAAyB,GAAG,GAAG,GAAG,WAAW,CAAC,QAAQ,EAAE,EACxD,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,EAC5C,cAAc,CACjB,CAAC;IACN,CAAC,CAAC;AACN,CAAC"}
|