@woltz/rich-domain 1.0.0 → 1.2.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/CHANGELOG.md +37 -86
- package/LICENSE +20 -20
- package/dist/base-entity.d.ts +1 -1
- package/dist/base-entity.d.ts.map +1 -1
- package/dist/base-entity.js +15 -19
- package/dist/base-entity.js.map +1 -1
- package/dist/constants.js +1 -4
- package/dist/constants.js.map +1 -1
- package/dist/criteria.d.ts +24 -14
- package/dist/criteria.d.ts.map +1 -1
- package/dist/criteria.js +154 -67
- package/dist/criteria.js.map +1 -1
- package/dist/deep-proxy.d.ts.map +1 -1
- package/dist/deep-proxy.js +19 -9
- package/dist/deep-proxy.js.map +1 -1
- package/dist/domain-event-bus.d.ts +5 -6
- package/dist/domain-event-bus.d.ts.map +1 -1
- package/dist/domain-event-bus.js +4 -17
- package/dist/domain-event-bus.js.map +1 -1
- package/dist/domain-event.d.ts +1 -31
- package/dist/domain-event.d.ts.map +1 -1
- package/dist/domain-event.js +4 -7
- package/dist/domain-event.js.map +1 -1
- package/dist/entity.d.ts +2 -2
- package/dist/entity.js +3 -8
- package/dist/entity.js.map +1 -1
- package/dist/exceptions.d.ts +251 -0
- package/dist/exceptions.d.ts.map +1 -0
- package/dist/exceptions.js +321 -0
- package/dist/exceptions.js.map +1 -0
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +14 -7
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -40
- package/dist/index.js.map +1 -1
- package/dist/mapper.js +1 -5
- package/dist/mapper.js.map +1 -1
- package/dist/paginated-result.d.ts.map +1 -1
- package/dist/paginated-result.js +17 -9
- package/dist/paginated-result.js.map +1 -1
- package/dist/repository/base-repository.js +4 -11
- package/dist/repository/base-repository.js.map +1 -1
- package/dist/repository/index.d.ts +1 -2
- package/dist/repository/index.d.ts.map +1 -1
- package/dist/repository/index.js +3 -26
- package/dist/repository/index.js.map +1 -1
- package/dist/repository/unit-of-work.d.ts +0 -11
- package/dist/repository/unit-of-work.d.ts.map +1 -1
- package/dist/repository/unit-of-work.js +2 -43
- package/dist/repository/unit-of-work.js.map +1 -1
- package/dist/types/criteria.d.ts +31 -7
- package/dist/types/criteria.d.ts.map +1 -1
- package/dist/types/criteria.js +1 -4
- package/dist/types/criteria.js.map +1 -1
- package/dist/types/domain-event.d.ts +32 -0
- package/dist/types/domain-event.d.ts.map +1 -0
- package/dist/types/domain-event.js +2 -0
- package/dist/types/domain-event.js.map +1 -0
- package/dist/types/domain.d.ts +2 -2
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/types/domain.js +1 -2
- package/dist/types/history-tracker.d.ts +1 -1
- package/dist/types/history-tracker.d.ts.map +1 -1
- package/dist/types/history-tracker.js +1 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -22
- package/dist/types/index.js.map +1 -1
- package/dist/types/standard-schema.js +1 -2
- package/dist/types/unit-of-work.js +1 -2
- package/dist/types/utils.js +1 -2
- package/dist/utils/criteria-operator-validation.d.ts +5 -0
- package/dist/utils/criteria-operator-validation.d.ts.map +1 -0
- package/dist/utils/criteria-operator-validation.js +143 -0
- package/dist/utils/criteria-operator-validation.js.map +1 -0
- package/dist/utils/helpers.d.ts +2 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +10 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/validation-error.js +3 -9
- package/dist/validation-error.js.map +1 -1
- package/dist/value-object.d.ts +1 -1
- package/dist/value-object.d.ts.map +1 -1
- package/dist/value-object.js +7 -14
- package/dist/value-object.js.map +1 -1
- package/eslint.config.js +9 -3
- package/jest.config.js +1 -1
- package/package.json +14 -20
- package/src/base-entity.ts +3 -3
- package/src/criteria.ts +268 -87
- package/src/deep-proxy.ts +50 -38
- package/src/domain-event-bus.ts +152 -166
- package/src/domain-event.ts +53 -90
- package/src/entity.ts +16 -16
- package/src/exceptions.ts +435 -0
- package/src/id.ts +107 -94
- package/src/index.ts +32 -8
- package/src/paginated-result.ts +15 -3
- package/src/repository/index.ts +1 -6
- package/src/repository/unit-of-work.ts +1 -44
- package/src/types/criteria.ts +95 -17
- package/src/types/domain-event.ts +38 -0
- package/src/types/domain.ts +2 -3
- package/src/types/history-tracker.ts +1 -1
- package/src/types/index.ts +1 -0
- package/src/utils/criteria-operator-validation.ts +171 -0
- package/src/utils/helpers.ts +6 -0
- package/src/validation-error.ts +97 -97
- package/src/value-object.ts +3 -6
- package/tests/criteria.test.ts +324 -1
- package/tests/domain-events.test.ts +431 -445
- package/tests/entity-validation.test.ts +1 -1
- package/tests/entity.test.ts +33 -33
- package/tests/repository.test.ts +4 -2
- package/tests/utils.ts +254 -151
- package/tests/value-object-validation.test.ts +0 -9
- package/tsconfig.json +2 -24
- package/.github/workflows/ci.yml +0 -40
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.vscode/settings.json +0 -3
- package/commitlint.config.js +0 -23
- package/dist/repository/in-memory-repository.d.ts +0 -50
- package/dist/repository/in-memory-repository.d.ts.map +0 -1
- package/dist/repository/in-memory-repository.js +0 -97
- package/dist/repository/in-memory-repository.js.map +0 -1
- package/src/repository/in-memory-repository.ts +0 -116
package/src/domain-event.ts
CHANGED
|
@@ -1,90 +1,53 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Domain Events - Event-Driven Architecture Support
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
readonly
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Convert event to JSON
|
|
59
|
-
*/
|
|
60
|
-
toJSON(): Record<string, any> {
|
|
61
|
-
return {
|
|
62
|
-
eventId: this.eventId,
|
|
63
|
-
eventName: this.eventName,
|
|
64
|
-
occurredOn: this.occurredOn.toISOString(),
|
|
65
|
-
aggregateId: this.aggregateId,
|
|
66
|
-
...this.getPayload(),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Override this to provide event-specific data
|
|
72
|
-
*/
|
|
73
|
-
protected getPayload(): Record<string, any> {
|
|
74
|
-
return {};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Event handler function type
|
|
80
|
-
*/
|
|
81
|
-
export type DomainEventHandler<T extends IDomainEvent = IDomainEvent> = (
|
|
82
|
-
event: T
|
|
83
|
-
) => void | Promise<void>;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Event handler class type
|
|
87
|
-
*/
|
|
88
|
-
export interface IDomainEventHandler<T extends IDomainEvent = IDomainEvent> {
|
|
89
|
-
handle(event: T): void | Promise<void>;
|
|
90
|
-
}
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Domain Events - Event-Driven Architecture Support
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { IDomainEvent } from ".";
|
|
6
|
+
import { Id } from "./id";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base class for domain events
|
|
10
|
+
*/
|
|
11
|
+
export abstract class DomainEvent implements IDomainEvent {
|
|
12
|
+
public readonly eventId: string;
|
|
13
|
+
public readonly occurredOn: Date;
|
|
14
|
+
public readonly aggregateId: string;
|
|
15
|
+
|
|
16
|
+
constructor(aggregateId: Id | string) {
|
|
17
|
+
this.eventId = this.generateEventId();
|
|
18
|
+
this.occurredOn = new Date();
|
|
19
|
+
this.aggregateId =
|
|
20
|
+
aggregateId instanceof Id ? aggregateId.value : aggregateId;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the event name (defaults to class name)
|
|
25
|
+
*/
|
|
26
|
+
get eventName(): string {
|
|
27
|
+
return this.constructor.name;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private generateEventId(): string {
|
|
31
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Convert event to JSON
|
|
36
|
+
*/
|
|
37
|
+
toJSON(): Record<string, any> {
|
|
38
|
+
return {
|
|
39
|
+
eventId: this.eventId,
|
|
40
|
+
eventName: this.eventName,
|
|
41
|
+
occurredOn: this.occurredOn.toISOString(),
|
|
42
|
+
aggregateId: this.aggregateId,
|
|
43
|
+
...this.getPayload(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Override this to provide event-specific data
|
|
49
|
+
*/
|
|
50
|
+
protected getPayload(): Record<string, any> {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/entity.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Entity & Aggregate Classes
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
|
-
import { BaseEntity } from
|
|
6
|
-
import { BaseProps } from
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Entity - Has identity and lifecycle, but is not an aggregate root
|
|
10
|
-
*/
|
|
11
|
-
export class Entity<T extends BaseProps> extends BaseEntity<T> {}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Aggregate - Aggregate root that manages a consistency boundary
|
|
15
|
-
*/
|
|
16
|
-
export class Aggregate<T extends BaseProps> extends BaseEntity<T> {}
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Entity & Aggregate Classes
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { BaseEntity } from "./base-entity";
|
|
6
|
+
import { BaseProps } from "./types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Entity - Has identity and lifecycle, but is not an aggregate root
|
|
10
|
+
*/
|
|
11
|
+
export class Entity<T extends BaseProps> extends BaseEntity<T> {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Aggregate - Aggregate root that manages a consistency boundary
|
|
15
|
+
*/
|
|
16
|
+
export class Aggregate<T extends BaseProps> extends BaseEntity<T> {}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base exception class for all Rich Domain exceptions
|
|
3
|
+
*/
|
|
4
|
+
abstract class DomainException extends Error {
|
|
5
|
+
public readonly code: string;
|
|
6
|
+
public readonly timestamp: Date;
|
|
7
|
+
public readonly __isDomainException = true;
|
|
8
|
+
|
|
9
|
+
constructor(message: string, code?: string) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = this.constructor.name;
|
|
12
|
+
this.code = code || this.constructor.name;
|
|
13
|
+
this.timestamp = new Date();
|
|
14
|
+
|
|
15
|
+
// Maintain proper stack trace
|
|
16
|
+
if (Error.captureStackTrace) {
|
|
17
|
+
Error.captureStackTrace(this, this.constructor);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if an error is a DomainException
|
|
23
|
+
*/
|
|
24
|
+
static isDomainException(error: unknown): error is DomainException {
|
|
25
|
+
return (
|
|
26
|
+
error instanceof DomainException ||
|
|
27
|
+
(error instanceof Error &&
|
|
28
|
+
"__isDomainException" in error &&
|
|
29
|
+
(error as any).__isDomainException === true)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Convert to JSON for serialization
|
|
35
|
+
*/
|
|
36
|
+
toJSON(): {
|
|
37
|
+
name: string;
|
|
38
|
+
message: string;
|
|
39
|
+
code: string;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
} {
|
|
42
|
+
return {
|
|
43
|
+
name: this.name,
|
|
44
|
+
message: this.message,
|
|
45
|
+
code: this.code,
|
|
46
|
+
timestamp: this.timestamp.toISOString(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Thrown when a domain rule or business logic is violated
|
|
53
|
+
*/
|
|
54
|
+
export class DomainError extends DomainException {
|
|
55
|
+
constructor(message: string, code?: string) {
|
|
56
|
+
super(message, code || "DOMAIN_ERROR");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Entity & Aggregate Exceptions
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Thrown when an entity or aggregate is not found
|
|
66
|
+
*/
|
|
67
|
+
export class EntityNotFoundError extends DomainException {
|
|
68
|
+
public readonly entityType: string;
|
|
69
|
+
public readonly entityId?: string;
|
|
70
|
+
|
|
71
|
+
constructor(entityType: string, entityId?: string, message?: string) {
|
|
72
|
+
const defaultMessage = entityId
|
|
73
|
+
? `${entityType} with id '${entityId}' not found`
|
|
74
|
+
: `${entityType} not found`;
|
|
75
|
+
|
|
76
|
+
super(message || defaultMessage, "ENTITY_NOT_FOUND");
|
|
77
|
+
this.entityType = entityType;
|
|
78
|
+
this.entityId = entityId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
toJSON() {
|
|
82
|
+
return {
|
|
83
|
+
...super.toJSON(),
|
|
84
|
+
entityType: this.entityType,
|
|
85
|
+
entityId: this.entityId,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Thrown when trying to create an entity that already exists
|
|
92
|
+
*/
|
|
93
|
+
export class EntityAlreadyExistsError extends DomainException {
|
|
94
|
+
public readonly entityType: string;
|
|
95
|
+
public readonly entityId?: string;
|
|
96
|
+
|
|
97
|
+
constructor(entityType: string, entityId?: string, message?: string) {
|
|
98
|
+
const defaultMessage = entityId
|
|
99
|
+
? `${entityType} with id '${entityId}' already exists`
|
|
100
|
+
: `${entityType} already exists`;
|
|
101
|
+
|
|
102
|
+
super(message || defaultMessage, "ENTITY_ALREADY_EXISTS");
|
|
103
|
+
this.entityType = entityType;
|
|
104
|
+
this.entityId = entityId;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
toJSON() {
|
|
108
|
+
return {
|
|
109
|
+
...super.toJSON(),
|
|
110
|
+
entityType: this.entityType,
|
|
111
|
+
entityId: this.entityId,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Repository & Persistence Exceptions
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Base exception for repository operations
|
|
122
|
+
*/
|
|
123
|
+
export class RepositoryError extends DomainException {
|
|
124
|
+
constructor(message: string, code?: string) {
|
|
125
|
+
super(message, code || "REPOSITORY_ERROR");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Thrown when a persistence operation fails
|
|
131
|
+
*/
|
|
132
|
+
export class PersistenceError extends RepositoryError {
|
|
133
|
+
public readonly operation: string;
|
|
134
|
+
public readonly cause?: Error;
|
|
135
|
+
|
|
136
|
+
constructor(operation: string, message?: string, cause?: Error) {
|
|
137
|
+
const defaultMessage = `Persistence operation '${operation}' failed${
|
|
138
|
+
message ? `: ${message}` : ""
|
|
139
|
+
}`;
|
|
140
|
+
|
|
141
|
+
super(defaultMessage, "PERSISTENCE_ERROR");
|
|
142
|
+
this.operation = operation;
|
|
143
|
+
this.cause = cause;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
toJSON() {
|
|
147
|
+
return {
|
|
148
|
+
...super.toJSON(),
|
|
149
|
+
operation: this.operation,
|
|
150
|
+
cause: this.cause?.message,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Thrown when a concurrency conflict occurs (optimistic locking)
|
|
157
|
+
*/
|
|
158
|
+
export class ConcurrencyError extends RepositoryError {
|
|
159
|
+
public readonly entityType: string;
|
|
160
|
+
public readonly entityId: string;
|
|
161
|
+
|
|
162
|
+
constructor(entityType: string, entityId: string, message?: string) {
|
|
163
|
+
const defaultMessage =
|
|
164
|
+
message ||
|
|
165
|
+
`Concurrency conflict detected for ${entityType} with id '${entityId}'`;
|
|
166
|
+
|
|
167
|
+
super(defaultMessage, "CONCURRENCY_ERROR");
|
|
168
|
+
this.entityType = entityType;
|
|
169
|
+
this.entityId = entityId;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
toJSON() {
|
|
173
|
+
return {
|
|
174
|
+
...super.toJSON(),
|
|
175
|
+
entityType: this.entityType,
|
|
176
|
+
entityId: this.entityId,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Thrown when a database constraint is violated
|
|
183
|
+
*/
|
|
184
|
+
export class ConstraintViolationError extends RepositoryError {
|
|
185
|
+
public readonly constraint: string;
|
|
186
|
+
|
|
187
|
+
constructor(constraint: string, message?: string) {
|
|
188
|
+
const defaultMessage =
|
|
189
|
+
message || `Database constraint '${constraint}' violated`;
|
|
190
|
+
|
|
191
|
+
super(defaultMessage, "CONSTRAINT_VIOLATION");
|
|
192
|
+
this.constraint = constraint;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
toJSON() {
|
|
196
|
+
return {
|
|
197
|
+
...super.toJSON(),
|
|
198
|
+
constraint: this.constraint,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Value Object Exceptions
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Thrown when a value object has invalid data
|
|
209
|
+
*/
|
|
210
|
+
export class InvalidValueObjectError extends DomainException {
|
|
211
|
+
public readonly valueObjectType: string;
|
|
212
|
+
public readonly invalidValue?: any;
|
|
213
|
+
|
|
214
|
+
constructor(valueObjectType: string, message: string, invalidValue?: any) {
|
|
215
|
+
super(message, "INVALID_VALUE_OBJECT");
|
|
216
|
+
this.valueObjectType = valueObjectType;
|
|
217
|
+
this.invalidValue = invalidValue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
toJSON() {
|
|
221
|
+
return {
|
|
222
|
+
...super.toJSON(),
|
|
223
|
+
valueObjectType: this.valueObjectType,
|
|
224
|
+
invalidValue: this.invalidValue,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ============================================================================
|
|
230
|
+
// Domain Event Exceptions
|
|
231
|
+
// ============================================================================
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Thrown when a domain event operation fails
|
|
235
|
+
*/
|
|
236
|
+
export class DomainEventError extends DomainException {
|
|
237
|
+
public readonly eventType?: string;
|
|
238
|
+
|
|
239
|
+
constructor(message: string, eventType?: string) {
|
|
240
|
+
super(message, "DOMAIN_EVENT_ERROR");
|
|
241
|
+
this.eventType = eventType;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
toJSON() {
|
|
245
|
+
return {
|
|
246
|
+
...super.toJSON(),
|
|
247
|
+
eventType: this.eventType,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Thrown when an event handler fails
|
|
254
|
+
*/
|
|
255
|
+
export class EventHandlerError extends DomainEventError {
|
|
256
|
+
public readonly handlerName: string;
|
|
257
|
+
public readonly cause?: Error;
|
|
258
|
+
|
|
259
|
+
constructor(handlerName: string, eventType: string, cause?: Error) {
|
|
260
|
+
const message = `Event handler '${handlerName}' failed for event '${eventType}'${
|
|
261
|
+
cause ? `: ${cause.message}` : ""
|
|
262
|
+
}`;
|
|
263
|
+
|
|
264
|
+
super(message, eventType);
|
|
265
|
+
this.handlerName = handlerName;
|
|
266
|
+
this.cause = cause;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
toJSON() {
|
|
270
|
+
return {
|
|
271
|
+
...super.toJSON(),
|
|
272
|
+
handlerName: this.handlerName,
|
|
273
|
+
cause: this.cause?.message,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// Criteria & Query Exceptions
|
|
280
|
+
// ============================================================================
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Thrown when a criteria or query is invalid
|
|
284
|
+
*/
|
|
285
|
+
export class InvalidCriteriaError extends DomainException {
|
|
286
|
+
public readonly field?: string;
|
|
287
|
+
|
|
288
|
+
constructor(message: string, field?: string) {
|
|
289
|
+
super(message, "INVALID_CRITERIA");
|
|
290
|
+
this.field = field;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
toJSON() {
|
|
294
|
+
return {
|
|
295
|
+
...super.toJSON(),
|
|
296
|
+
field: this.field,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// Unit of Work Exceptions
|
|
303
|
+
// ============================================================================
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Thrown when a transaction operation fails
|
|
307
|
+
*/
|
|
308
|
+
export class TransactionError extends DomainException {
|
|
309
|
+
public readonly operation: string;
|
|
310
|
+
public readonly cause?: Error;
|
|
311
|
+
|
|
312
|
+
constructor(operation: string, message?: string, cause?: Error) {
|
|
313
|
+
const defaultMessage = `Transaction ${operation} failed${
|
|
314
|
+
message ? `: ${message}` : ""
|
|
315
|
+
}`;
|
|
316
|
+
|
|
317
|
+
super(defaultMessage, "TRANSACTION_ERROR");
|
|
318
|
+
this.operation = operation;
|
|
319
|
+
this.cause = cause;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
toJSON() {
|
|
323
|
+
return {
|
|
324
|
+
...super.toJSON(),
|
|
325
|
+
operation: this.operation,
|
|
326
|
+
cause: this.cause?.message,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ============================================================================
|
|
332
|
+
// Generic/Unknown Exceptions
|
|
333
|
+
// ============================================================================
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Thrown when an unexpected or unknown error occurs
|
|
337
|
+
*/
|
|
338
|
+
export class UnknownError extends DomainException {
|
|
339
|
+
public readonly originalError?: Error;
|
|
340
|
+
|
|
341
|
+
constructor(message?: string, originalError?: Error) {
|
|
342
|
+
const defaultMessage =
|
|
343
|
+
message || originalError?.message || "An unknown error occurred";
|
|
344
|
+
|
|
345
|
+
super(defaultMessage, "UNKNOWN_ERROR");
|
|
346
|
+
this.originalError = originalError;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
toJSON() {
|
|
350
|
+
return {
|
|
351
|
+
...super.toJSON(),
|
|
352
|
+
originalError: this.originalError?.message,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Thrown when a feature is not implemented yet
|
|
359
|
+
*/
|
|
360
|
+
export class NotImplementedError extends DomainException {
|
|
361
|
+
public readonly feature: string;
|
|
362
|
+
|
|
363
|
+
constructor(feature: string, message?: string) {
|
|
364
|
+
const defaultMessage = message || `Feature '${feature}' is not implemented`;
|
|
365
|
+
|
|
366
|
+
super(defaultMessage, "NOT_IMPLEMENTED");
|
|
367
|
+
this.feature = feature;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
toJSON() {
|
|
371
|
+
return {
|
|
372
|
+
...super.toJSON(),
|
|
373
|
+
feature: this.feature,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Thrown when a required configuration is missing
|
|
380
|
+
*/
|
|
381
|
+
export class ConfigurationError extends DomainException {
|
|
382
|
+
public readonly configKey?: string;
|
|
383
|
+
|
|
384
|
+
constructor(message: string, configKey?: string) {
|
|
385
|
+
super(message, "CONFIGURATION_ERROR");
|
|
386
|
+
this.configKey = configKey;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
toJSON() {
|
|
390
|
+
return {
|
|
391
|
+
...super.toJSON(),
|
|
392
|
+
configKey: this.configKey,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ============================================================================
|
|
398
|
+
// Mapper Exceptions
|
|
399
|
+
// ============================================================================
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Thrown when mapping between domain and persistence fails
|
|
403
|
+
*/
|
|
404
|
+
export class MapperError extends DomainException {
|
|
405
|
+
public readonly direction: "toDomain" | "toPersistence";
|
|
406
|
+
public readonly entityType: string;
|
|
407
|
+
public readonly cause?: Error;
|
|
408
|
+
|
|
409
|
+
constructor(
|
|
410
|
+
direction: "toDomain" | "toPersistence",
|
|
411
|
+
entityType: string,
|
|
412
|
+
message?: string,
|
|
413
|
+
cause?: Error
|
|
414
|
+
) {
|
|
415
|
+
const defaultMessage =
|
|
416
|
+
message ||
|
|
417
|
+
`Failed to map ${entityType} ${
|
|
418
|
+
direction === "toDomain" ? "to domain" : "to persistence"
|
|
419
|
+
}`;
|
|
420
|
+
|
|
421
|
+
super(defaultMessage, "MAPPER_ERROR");
|
|
422
|
+
this.direction = direction;
|
|
423
|
+
this.entityType = entityType;
|
|
424
|
+
this.cause = cause;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
toJSON() {
|
|
428
|
+
return {
|
|
429
|
+
...super.toJSON(),
|
|
430
|
+
direction: this.direction,
|
|
431
|
+
entityType: this.entityType,
|
|
432
|
+
cause: this.cause?.message,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|