@velony/domain 2.0.0 → 3.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 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,11 +113,19 @@ class OrderId extends Id<string> {
120
113
  }
121
114
  }
122
115
 
123
- class OrderPlacedEvent extends DomainEvent<{ total: number }> {
124
- public readonly type = 'order.placed';
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
+ };
123
+ }
124
+ }
125
125
 
126
- constructor(aggregateId: string, total: number) {
127
- super(aggregateId, { total });
126
+ class OrderPlacedEvent extends DomainEvent<'order.placed'> {
127
+ constructor(aggregateId: OrderId, total: number) {
128
+ super('order.placed', aggregateId, { total });
128
129
  }
129
130
  }
130
131
 
@@ -138,7 +139,7 @@ class Order extends AggregateRoot<OrderId> {
138
139
 
139
140
  place(): void {
140
141
  this.pushDomainEvent(
141
- new OrderPlacedEvent(this.id.toString(), this.total)
142
+ new OrderPlacedEvent(this.id, this.total)
142
143
  );
143
144
  }
144
145
  }
@@ -150,28 +151,42 @@ const events = order.pullDomainEvents();
150
151
 
151
152
  ### Domain Event
152
153
 
153
- Represents something significant that happened in the domain.
154
+ Represents something significant that happened in the domain. Events use a type-safe registry pattern for compile-time validation.
154
155
 
155
156
  ```typescript
156
- import { DomainEvent } from '@velony/domain';
157
+ import { DomainEvent, DomainEventRegistry, Id } from '@velony/domain';
157
158
 
158
- interface UserRegisteredPayload {
159
- email: string;
160
- name: string;
159
+ class UserId extends Id<string> {
160
+ static create(value: string): UserId {
161
+ return new UserId(value);
162
+ }
161
163
  }
162
164
 
163
- class UserRegisteredEvent extends DomainEvent<UserRegisteredPayload> {
164
- public readonly type = 'user.registered';
165
+ // Register event types in the DomainEventRegistry
166
+ declare module '@velony/domain' {
167
+ interface DomainEventRegistry {
168
+ 'user.registered': {
169
+ aggregateId: UserId;
170
+ payload: {
171
+ email: string;
172
+ name: string;
173
+ };
174
+ };
175
+ }
176
+ }
165
177
 
166
- constructor(aggregateId: string, email: string, name: string) {
167
- super(aggregateId, { email, name });
178
+ class UserRegisteredEvent extends DomainEvent<'user.registered'> {
179
+ constructor(aggregateId: UserId, email: string, name: string) {
180
+ super('user.registered', aggregateId, { email, name });
168
181
  }
169
182
  }
170
183
 
171
- const event = new UserRegisteredEvent('user-123', 'john@example.com', 'John Doe');
184
+ const userId = UserId.create('user-123');
185
+ const event = new UserRegisteredEvent(userId, 'john@example.com', 'John Doe');
172
186
  console.log(event.id); // UUIDv7
173
187
  console.log(event.type); // "user.registered"
174
188
  console.log(event.occurredAt); // Date
189
+ console.log(event.aggregateId); // UserId instance
175
190
  console.log(event.payload); // { email: "john@example.com", name: "John Doe" }
176
191
  ```
177
192
 
@@ -214,15 +229,29 @@ StoragePath.create('files//data'); // Error: Storage path contains invalid doubl
214
229
 
215
230
  ### `AggregateRoot<TIdentifier>`
216
231
  - Extends `Entity<TIdentifier>`
217
- - `pullDomainEvents(): DomainEvent<any, any>[]` - Retrieve and clear events
218
- - `pushDomainEvent(event: DomainEvent<any, any>): void` - Add event (protected)
232
+ - `pullDomainEvents(): AnyDomainEvent[]` - Retrieve and clear events
233
+ - `pushDomainEvent(event: AnyDomainEvent): void` - Add event (protected)
219
234
 
220
- ### `DomainEvent<TPayload>`
235
+ ### `DomainEvent<TType>`
221
236
  - `id: string` - Unique event ID (UUIDv7)
222
- - `aggregateId: string` - ID of the aggregate that produced the event
223
- - `payload: TPayload` - Event-specific data
237
+ - `type: TType` - Type identifier for the event (must be registered in DomainEventRegistry)
238
+ - `aggregateId: DomainEventRegistry[TType]['aggregateId']` - ID of the aggregate that produced the event
239
+ - `payload: DomainEventRegistry[TType]['payload']` - Event-specific data
224
240
  - `occurredAt: Date` - Timestamp of occurrence
225
- - `type: string` - Event type identifier (abstract, must be implemented)
241
+
242
+ ### `DomainEventRegistry`
243
+ Interface for registering event types with their aggregate ID and payload types. Extend this interface using module augmentation to register new event types:
244
+
245
+ ```typescript
246
+ declare module '@velony/domain' {
247
+ interface DomainEventRegistry {
248
+ 'your.event.type': {
249
+ aggregateId: YourAggregateId;
250
+ payload: YourPayloadType;
251
+ };
252
+ }
253
+ }
254
+ ```
226
255
 
227
256
  ## License
228
257
 
@@ -1,4 +1,4 @@
1
- import { DomainEvent } from './domain-event';
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(): DomainEvent<any>[];
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: DomainEvent<any>): void;
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,GAAuB,EAAE,CAAC;IAE/C;;;;;;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,KAAuB;QAC/C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;CACF;AAlCD,sCAkCC"}
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"}
@@ -1,18 +1,28 @@
1
- export declare const DOMAIN_EVENT_BRAND: unique symbol;
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 TPayload - The type of the event payload containing event-specific data
18
+ * @template TType - The type identifier for the event (must be registered in DomainEventRegistry)
8
19
  */
9
- export declare abstract class DomainEvent<TPayload> {
20
+ export declare abstract class DomainEvent<TType extends keyof DomainEventRegistry> {
10
21
  private readonly [DOMAIN_EVENT_BRAND];
11
22
  /**
12
- * Static type identifier for the event class.
13
- * Should be overridden in concrete event classes.
23
+ * Type identifier for the event.
14
24
  */
15
- static readonly type: string;
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: string;
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: TPayload;
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
52
  * @protected
42
53
  */
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
48
- */
49
- get type(): string;
54
+ protected 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 {};
@@ -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 TPayload - The type of the event payload containing event-specific data
10
+ * @template TType - The type identifier for the event (must be registered in DomainEventRegistry)
11
11
  */
12
12
  class DomainEvent {
13
- [exports.DOMAIN_EVENT_BRAND];
13
+ [DOMAIN_EVENT_BRAND];
14
14
  /**
15
- * Static type identifier for the event class.
16
- * Should be overridden in concrete event classes.
15
+ * Type identifier for the event.
17
16
  */
18
- static type;
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
  * @protected
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
@@ -1 +1 @@
1
- {"version":3,"file":"domain-event.js","sourceRoot":"","sources":["../src/domain-event.ts"],"names":[],"mappings":";;;AAAA,+BAAoC;AAIpC;;;;;;GAMG;AACH,MAAsB,WAAW;IACd,CAAC,0BAAkB,CAAC,CAAO;IAE5C;;;OAGG;IACI,MAAM,CAAU,IAAI,CAAS;IAEpC;;;OAGG;IACa,EAAE,CAAS;IAE3B;;;OAGG;IACa,WAAW,CAAS;IAEpC;;;OAGG;IACa,OAAO,CAAW;IAElC;;;OAGG;IACa,UAAU,CAAO;IAEjC;;;;;;OAMG;IACH,YAAsB,WAAmB,EAAE,OAAiB;QAC1D,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;IAED;;;;OAIG;IACH,IAAW,IAAI;QACb,OAAQ,IAAI,CAAC,WAA4C,CAAC,IAAI,CAAC;IACjE,CAAC;CACF;AAvDD,kCAuDC"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velony/domain",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "TypeScript library providing core building blocks for Domain-Driven Design (DDD) applications",
5
5
  "keywords": [
6
6
  "ddd",