@velony/domain 2.0.0 → 3.1.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 +55 -31
- package/dist/aggregate-root.d.ts +3 -3
- package/dist/aggregate-root.js.map +1 -1
- package/dist/domain-event.d.ts +27 -16
- package/dist/domain-event.js +8 -15
- package/dist/domain-event.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,13 +8,6 @@ A TypeScript library providing core building blocks for Domain-Driven Design (DD
|
|
|
8
8
|
npm install @velony/domain
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
## Features
|
|
12
|
-
|
|
13
|
-
- **Type-safe** - Full TypeScript support with comprehensive type definitions
|
|
14
|
-
- **DDD Patterns** - Implementations of core DDD tactical patterns
|
|
15
|
-
- **Immutable** - Value objects and events are immutable by design
|
|
16
|
-
- **Minimal dependencies** - Only depends on `uuid` for event ID generation
|
|
17
|
-
|
|
18
11
|
## Core Concepts
|
|
19
12
|
|
|
20
13
|
### Entity
|
|
@@ -112,7 +105,7 @@ console.log(age.toString()); // "25"
|
|
|
112
105
|
The entry point to an aggregate that maintains consistency boundaries and manages domain events.
|
|
113
106
|
|
|
114
107
|
```typescript
|
|
115
|
-
import { AggregateRoot, Id, DomainEvent } from '@velony/domain';
|
|
108
|
+
import { AggregateRoot, Id, DomainEvent, DomainEventRegistry } from '@velony/domain';
|
|
116
109
|
|
|
117
110
|
class OrderId extends Id<string> {
|
|
118
111
|
static create(value: string): OrderId {
|
|
@@ -120,14 +113,18 @@ class OrderId extends Id<string> {
|
|
|
120
113
|
}
|
|
121
114
|
}
|
|
122
115
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
// Register the event type in the registry
|
|
117
|
+
declare module '@velony/domain' {
|
|
118
|
+
interface DomainEventRegistry {
|
|
119
|
+
'order.placed': {
|
|
120
|
+
aggregateId: OrderId;
|
|
121
|
+
payload: { total: number };
|
|
122
|
+
};
|
|
128
123
|
}
|
|
129
124
|
}
|
|
130
125
|
|
|
126
|
+
class OrderPlacedEvent extends DomainEvent<'order.placed'> {}
|
|
127
|
+
|
|
131
128
|
class Order extends AggregateRoot<OrderId> {
|
|
132
129
|
constructor(
|
|
133
130
|
id: OrderId,
|
|
@@ -138,7 +135,7 @@ class Order extends AggregateRoot<OrderId> {
|
|
|
138
135
|
|
|
139
136
|
place(): void {
|
|
140
137
|
this.pushDomainEvent(
|
|
141
|
-
new OrderPlacedEvent(this.id
|
|
138
|
+
new OrderPlacedEvent('order.placed', this.id, { total: this.total })
|
|
142
139
|
);
|
|
143
140
|
}
|
|
144
141
|
}
|
|
@@ -150,28 +147,41 @@ const events = order.pullDomainEvents();
|
|
|
150
147
|
|
|
151
148
|
### Domain Event
|
|
152
149
|
|
|
153
|
-
Represents something significant that happened in the domain.
|
|
150
|
+
Represents something significant that happened in the domain. Events use a type-safe registry pattern for compile-time validation.
|
|
154
151
|
|
|
155
152
|
```typescript
|
|
156
|
-
import { DomainEvent } from '@velony/domain';
|
|
153
|
+
import { DomainEvent, DomainEventRegistry, Id } from '@velony/domain';
|
|
157
154
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
155
|
+
class UserId extends Id<string> {
|
|
156
|
+
static create(value: string): UserId {
|
|
157
|
+
return new UserId(value);
|
|
158
|
+
}
|
|
161
159
|
}
|
|
162
160
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
// Register event types in the DomainEventRegistry
|
|
162
|
+
declare module '@velony/domain' {
|
|
163
|
+
interface DomainEventRegistry {
|
|
164
|
+
'user.registered': {
|
|
165
|
+
aggregateId: UserId;
|
|
166
|
+
payload: {
|
|
167
|
+
email: string;
|
|
168
|
+
name: string;
|
|
169
|
+
};
|
|
170
|
+
};
|
|
168
171
|
}
|
|
169
172
|
}
|
|
170
173
|
|
|
171
|
-
|
|
174
|
+
class UserRegisteredEvent extends DomainEvent<'user.registered'> {}
|
|
175
|
+
|
|
176
|
+
const userId = UserId.create('user-123');
|
|
177
|
+
const event = new UserRegisteredEvent('user.registered', userId, {
|
|
178
|
+
email: 'john@example.com',
|
|
179
|
+
name: 'John Doe'
|
|
180
|
+
});
|
|
172
181
|
console.log(event.id); // UUIDv7
|
|
173
182
|
console.log(event.type); // "user.registered"
|
|
174
183
|
console.log(event.occurredAt); // Date
|
|
184
|
+
console.log(event.aggregateId); // UserId instance
|
|
175
185
|
console.log(event.payload); // { email: "john@example.com", name: "John Doe" }
|
|
176
186
|
```
|
|
177
187
|
|
|
@@ -214,15 +224,29 @@ StoragePath.create('files//data'); // Error: Storage path contains invalid doubl
|
|
|
214
224
|
|
|
215
225
|
### `AggregateRoot<TIdentifier>`
|
|
216
226
|
- Extends `Entity<TIdentifier>`
|
|
217
|
-
- `pullDomainEvents():
|
|
218
|
-
- `pushDomainEvent(event:
|
|
227
|
+
- `pullDomainEvents(): AnyDomainEvent[]` - Retrieve and clear events
|
|
228
|
+
- `pushDomainEvent(event: AnyDomainEvent): void` - Add event (protected)
|
|
219
229
|
|
|
220
|
-
### `DomainEvent<
|
|
230
|
+
### `DomainEvent<TType>`
|
|
221
231
|
- `id: string` - Unique event ID (UUIDv7)
|
|
222
|
-
- `
|
|
223
|
-
- `
|
|
232
|
+
- `type: TType` - Type identifier for the event (must be registered in DomainEventRegistry)
|
|
233
|
+
- `aggregateId: DomainEventRegistry[TType]['aggregateId']` - ID of the aggregate that produced the event
|
|
234
|
+
- `payload: DomainEventRegistry[TType]['payload']` - Event-specific data
|
|
224
235
|
- `occurredAt: Date` - Timestamp of occurrence
|
|
225
|
-
|
|
236
|
+
|
|
237
|
+
### `DomainEventRegistry`
|
|
238
|
+
Interface for registering event types with their aggregate ID and payload types. Extend this interface using module augmentation to register new event types:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
declare module '@velony/domain' {
|
|
242
|
+
interface DomainEventRegistry {
|
|
243
|
+
'your.event.type': {
|
|
244
|
+
aggregateId: YourAggregateId;
|
|
245
|
+
payload: YourPayloadType;
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
226
250
|
|
|
227
251
|
## License
|
|
228
252
|
|
package/dist/aggregate-root.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type AnyDomainEvent } from './domain-event';
|
|
2
2
|
import { Entity } from './entity';
|
|
3
3
|
import { Id } from './id';
|
|
4
4
|
declare const AGGREGATE_ROOT_BRAND: unique symbol;
|
|
@@ -25,7 +25,7 @@ export declare abstract class AggregateRoot<TIdentifier extends Id<string | numb
|
|
|
25
25
|
*
|
|
26
26
|
* @returns An array of domain events that occurred within the aggregate
|
|
27
27
|
*/
|
|
28
|
-
pullDomainEvents():
|
|
28
|
+
pullDomainEvents(): AnyDomainEvent[];
|
|
29
29
|
/**
|
|
30
30
|
* Adds a domain event to the aggregate's event collection.
|
|
31
31
|
* Protected to allow only the aggregate itself to record events.
|
|
@@ -33,6 +33,6 @@ export declare abstract class AggregateRoot<TIdentifier extends Id<string | numb
|
|
|
33
33
|
* @param event - The domain event to add
|
|
34
34
|
* @protected
|
|
35
35
|
*/
|
|
36
|
-
protected pushDomainEvent(event:
|
|
36
|
+
protected pushDomainEvent(event: AnyDomainEvent): void;
|
|
37
37
|
}
|
|
38
38
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate-root.js","sourceRoot":"","sources":["../src/aggregate-root.ts"],"names":[],"mappings":";;;AACA,qCAAkC;AAKlC;;;;;;;;GAQG;AACH,MAAsB,aAEpB,SAAQ,eAAmB;IACV,CAAC,oBAAoB,CAAC,CAAO;IAE9C;;;OAGG;IACK,aAAa,
|
|
1
|
+
{"version":3,"file":"aggregate-root.js","sourceRoot":"","sources":["../src/aggregate-root.ts"],"names":[],"mappings":";;;AACA,qCAAkC;AAKlC;;;;;;;;GAQG;AACH,MAAsB,aAEpB,SAAQ,eAAmB;IACV,CAAC,oBAAoB,CAAC,CAAO;IAE9C;;;OAGG;IACK,aAAa,GAAqB,EAAE,CAAC;IAE7C;;;;;;OAMG;IACI,gBAAgB;QACrB,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;OAMG;IACO,eAAe,CAAC,KAAqB;QAC7C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;CACF;AAlCD,sCAkCC"}
|
package/dist/domain-event.d.ts
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
import { Id } from './id';
|
|
2
|
+
declare const DOMAIN_EVENT_BRAND: unique symbol;
|
|
3
|
+
/**
|
|
4
|
+
* Registry that maps event types to their aggregate and payload types.
|
|
5
|
+
* Extend this interface to register new event types.
|
|
6
|
+
*/
|
|
7
|
+
export interface DomainEventRegistry {
|
|
8
|
+
[key: string]: {
|
|
9
|
+
aggregateId: Id<string | number>;
|
|
10
|
+
payload: unknown;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* Abstract base class for domain events in Domain-Driven Design.
|
|
4
15
|
* A domain event represents something that happened in the domain that
|
|
5
16
|
* domain experts care about. Events are immutable and represent facts.
|
|
6
17
|
*
|
|
7
|
-
* @template
|
|
18
|
+
* @template TType - The type identifier for the event (must be registered in DomainEventRegistry)
|
|
8
19
|
*/
|
|
9
|
-
export declare abstract class DomainEvent<
|
|
20
|
+
export declare abstract class DomainEvent<TType extends keyof DomainEventRegistry> {
|
|
10
21
|
private readonly [DOMAIN_EVENT_BRAND];
|
|
11
22
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Should be overridden in concrete event classes.
|
|
23
|
+
* Type identifier for the event.
|
|
14
24
|
*/
|
|
15
|
-
|
|
25
|
+
readonly type: TType;
|
|
16
26
|
/**
|
|
17
27
|
* Unique identifier for this event instance (UUIDv7).
|
|
18
28
|
* @readonly
|
|
@@ -22,12 +32,12 @@ export declare abstract class DomainEvent<TPayload> {
|
|
|
22
32
|
* The identifier of the aggregate that produced this event.
|
|
23
33
|
* @readonly
|
|
24
34
|
*/
|
|
25
|
-
readonly aggregateId:
|
|
35
|
+
readonly aggregateId: DomainEventRegistry[TType]['aggregateId'];
|
|
26
36
|
/**
|
|
27
37
|
* The data payload specific to this event.
|
|
28
38
|
* @readonly
|
|
29
39
|
*/
|
|
30
|
-
readonly payload:
|
|
40
|
+
readonly payload: DomainEventRegistry[TType]['payload'];
|
|
31
41
|
/**
|
|
32
42
|
* The timestamp when this event occurred.
|
|
33
43
|
* @readonly
|
|
@@ -36,15 +46,16 @@ export declare abstract class DomainEvent<TPayload> {
|
|
|
36
46
|
/**
|
|
37
47
|
* Creates a new domain event.
|
|
38
48
|
*
|
|
49
|
+
* @param type - The event type identifier
|
|
39
50
|
* @param aggregateId - The ID of the aggregate that produced this event
|
|
40
51
|
* @param payload - The event-specific data
|
|
41
|
-
* @
|
|
42
|
-
*/
|
|
43
|
-
protected constructor(aggregateId: string, payload: TPayload);
|
|
44
|
-
/**
|
|
45
|
-
* Gets the type identifier for this event.
|
|
46
|
-
*
|
|
47
|
-
* @returns The event type string from the static type property
|
|
52
|
+
* @public
|
|
48
53
|
*/
|
|
49
|
-
|
|
54
|
+
constructor(type: TType, aggregateId: DomainEventRegistry[TType]['aggregateId'], payload: DomainEventRegistry[TType]['payload']);
|
|
50
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Union type representing any domain event registered in the DomainEventRegistry.
|
|
58
|
+
* Useful for type-safe collections and handlers that work with multiple event types.
|
|
59
|
+
*/
|
|
60
|
+
export type AnyDomainEvent = DomainEvent<keyof DomainEventRegistry>;
|
|
61
|
+
export {};
|
package/dist/domain-event.js
CHANGED
|
@@ -7,15 +7,14 @@ const uuid_1 = require("uuid");
|
|
|
7
7
|
* A domain event represents something that happened in the domain that
|
|
8
8
|
* domain experts care about. Events are immutable and represent facts.
|
|
9
9
|
*
|
|
10
|
-
* @template
|
|
10
|
+
* @template TType - The type identifier for the event (must be registered in DomainEventRegistry)
|
|
11
11
|
*/
|
|
12
12
|
class DomainEvent {
|
|
13
|
-
[
|
|
13
|
+
[DOMAIN_EVENT_BRAND];
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
* Should be overridden in concrete event classes.
|
|
15
|
+
* Type identifier for the event.
|
|
17
16
|
*/
|
|
18
|
-
|
|
17
|
+
type;
|
|
19
18
|
/**
|
|
20
19
|
* Unique identifier for this event instance (UUIDv7).
|
|
21
20
|
* @readonly
|
|
@@ -39,24 +38,18 @@ class DomainEvent {
|
|
|
39
38
|
/**
|
|
40
39
|
* Creates a new domain event.
|
|
41
40
|
*
|
|
41
|
+
* @param type - The event type identifier
|
|
42
42
|
* @param aggregateId - The ID of the aggregate that produced this event
|
|
43
43
|
* @param payload - The event-specific data
|
|
44
|
-
* @
|
|
44
|
+
* @public
|
|
45
45
|
*/
|
|
46
|
-
constructor(aggregateId, payload) {
|
|
46
|
+
constructor(type, aggregateId, payload) {
|
|
47
|
+
this.type = type;
|
|
47
48
|
this.id = (0, uuid_1.v7)();
|
|
48
49
|
this.aggregateId = aggregateId;
|
|
49
50
|
this.occurredAt = new Date();
|
|
50
51
|
this.payload = payload;
|
|
51
52
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Gets the type identifier for this event.
|
|
54
|
-
*
|
|
55
|
-
* @returns The event type string from the static type property
|
|
56
|
-
*/
|
|
57
|
-
get type() {
|
|
58
|
-
return this.constructor.type;
|
|
59
|
-
}
|
|
60
53
|
}
|
|
61
54
|
exports.DomainEvent = DomainEvent;
|
|
62
55
|
//# sourceMappingURL=domain-event.js.map
|
package/dist/domain-event.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain-event.js","sourceRoot":"","sources":["../src/domain-event.ts"],"names":[],"mappings":";;;AAAA,+BAAoC;
|
|
1
|
+
{"version":3,"file":"domain-event.js","sourceRoot":"","sources":["../src/domain-event.ts"],"names":[],"mappings":";;;AAAA,+BAAoC;AAgBpC;;;;;;GAMG;AACH,MAAsB,WAAW;IACd,CAAC,kBAAkB,CAAC,CAAO;IAE5C;;OAEG;IACa,IAAI,CAAQ;IAE5B;;;OAGG;IACa,EAAE,CAAS;IAE3B;;;OAGG;IACa,WAAW,CAA4C;IAEvE;;;OAGG;IACa,OAAO,CAAwC;IAE/D;;;OAGG;IACa,UAAU,CAAO;IAEjC;;;;;;;OAOG;IACH,YACE,IAAW,EACX,WAAsD,EACtD,OAA8C;QAE9C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,IAAA,SAAM,GAAE,CAAC;QACnB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAnDD,kCAmDC"}
|