@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 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
- class OrderPlacedEvent extends DomainEvent<{ total: number }> {
124
- public readonly type = 'order.placed';
125
-
126
- constructor(aggregateId: string, total: number) {
127
- super(aggregateId, { total });
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.toString(), this.total)
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
- interface UserRegisteredPayload {
159
- email: string;
160
- name: string;
155
+ class UserId extends Id<string> {
156
+ static create(value: string): UserId {
157
+ return new UserId(value);
158
+ }
161
159
  }
162
160
 
163
- class UserRegisteredEvent extends DomainEvent<UserRegisteredPayload> {
164
- public readonly type = 'user.registered';
165
-
166
- constructor(aggregateId: string, email: string, name: string) {
167
- super(aggregateId, { email, name });
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
- const event = new UserRegisteredEvent('user-123', 'john@example.com', 'John Doe');
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(): DomainEvent<any, any>[]` - Retrieve and clear events
218
- - `pushDomainEvent(event: DomainEvent<any, any>): void` - Add event (protected)
227
+ - `pullDomainEvents(): AnyDomainEvent[]` - Retrieve and clear events
228
+ - `pushDomainEvent(event: AnyDomainEvent): void` - Add event (protected)
219
229
 
220
- ### `DomainEvent<TPayload>`
230
+ ### `DomainEvent<TType>`
221
231
  - `id: string` - Unique event ID (UUIDv7)
222
- - `aggregateId: string` - ID of the aggregate that produced the event
223
- - `payload: TPayload` - Event-specific data
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
- - `type: string` - Event type identifier (abstract, must be implemented)
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
 
@@ -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
- * @protected
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
- get type(): string;
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 {};
@@ -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
- * @protected
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
@@ -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.1.0",
4
4
  "description": "TypeScript library providing core building blocks for Domain-Driven Design (DDD) applications",
5
5
  "keywords": [
6
6
  "ddd",