@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +37 -86
  2. package/LICENSE +20 -20
  3. package/dist/base-entity.d.ts +1 -1
  4. package/dist/base-entity.d.ts.map +1 -1
  5. package/dist/base-entity.js +15 -19
  6. package/dist/base-entity.js.map +1 -1
  7. package/dist/constants.js +1 -4
  8. package/dist/constants.js.map +1 -1
  9. package/dist/criteria.d.ts +24 -14
  10. package/dist/criteria.d.ts.map +1 -1
  11. package/dist/criteria.js +154 -67
  12. package/dist/criteria.js.map +1 -1
  13. package/dist/deep-proxy.d.ts.map +1 -1
  14. package/dist/deep-proxy.js +19 -9
  15. package/dist/deep-proxy.js.map +1 -1
  16. package/dist/domain-event-bus.d.ts +5 -6
  17. package/dist/domain-event-bus.d.ts.map +1 -1
  18. package/dist/domain-event-bus.js +4 -17
  19. package/dist/domain-event-bus.js.map +1 -1
  20. package/dist/domain-event.d.ts +1 -31
  21. package/dist/domain-event.d.ts.map +1 -1
  22. package/dist/domain-event.js +4 -7
  23. package/dist/domain-event.js.map +1 -1
  24. package/dist/entity.d.ts +2 -2
  25. package/dist/entity.js +3 -8
  26. package/dist/entity.js.map +1 -1
  27. package/dist/exceptions.d.ts +251 -0
  28. package/dist/exceptions.d.ts.map +1 -0
  29. package/dist/exceptions.js +321 -0
  30. package/dist/exceptions.js.map +1 -0
  31. package/dist/id.d.ts.map +1 -1
  32. package/dist/id.js +14 -7
  33. package/dist/id.js.map +1 -1
  34. package/dist/index.d.ts +2 -4
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +11 -40
  37. package/dist/index.js.map +1 -1
  38. package/dist/mapper.js +1 -5
  39. package/dist/mapper.js.map +1 -1
  40. package/dist/paginated-result.d.ts.map +1 -1
  41. package/dist/paginated-result.js +17 -9
  42. package/dist/paginated-result.js.map +1 -1
  43. package/dist/repository/base-repository.js +4 -11
  44. package/dist/repository/base-repository.js.map +1 -1
  45. package/dist/repository/index.d.ts +1 -2
  46. package/dist/repository/index.d.ts.map +1 -1
  47. package/dist/repository/index.js +3 -26
  48. package/dist/repository/index.js.map +1 -1
  49. package/dist/repository/unit-of-work.d.ts +0 -11
  50. package/dist/repository/unit-of-work.d.ts.map +1 -1
  51. package/dist/repository/unit-of-work.js +2 -43
  52. package/dist/repository/unit-of-work.js.map +1 -1
  53. package/dist/types/criteria.d.ts +31 -7
  54. package/dist/types/criteria.d.ts.map +1 -1
  55. package/dist/types/criteria.js +1 -4
  56. package/dist/types/criteria.js.map +1 -1
  57. package/dist/types/domain-event.d.ts +32 -0
  58. package/dist/types/domain-event.d.ts.map +1 -0
  59. package/dist/types/domain-event.js +2 -0
  60. package/dist/types/domain-event.js.map +1 -0
  61. package/dist/types/domain.d.ts +2 -2
  62. package/dist/types/domain.d.ts.map +1 -1
  63. package/dist/types/domain.js +1 -2
  64. package/dist/types/history-tracker.d.ts +1 -1
  65. package/dist/types/history-tracker.d.ts.map +1 -1
  66. package/dist/types/history-tracker.js +1 -2
  67. package/dist/types/index.d.ts +1 -0
  68. package/dist/types/index.d.ts.map +1 -1
  69. package/dist/types/index.js +7 -22
  70. package/dist/types/index.js.map +1 -1
  71. package/dist/types/standard-schema.js +1 -2
  72. package/dist/types/unit-of-work.js +1 -2
  73. package/dist/types/utils.js +1 -2
  74. package/dist/utils/criteria-operator-validation.d.ts +5 -0
  75. package/dist/utils/criteria-operator-validation.d.ts.map +1 -0
  76. package/dist/utils/criteria-operator-validation.js +143 -0
  77. package/dist/utils/criteria-operator-validation.js.map +1 -0
  78. package/dist/utils/helpers.d.ts +2 -0
  79. package/dist/utils/helpers.d.ts.map +1 -0
  80. package/dist/utils/helpers.js +10 -0
  81. package/dist/utils/helpers.js.map +1 -0
  82. package/dist/validation-error.js +3 -9
  83. package/dist/validation-error.js.map +1 -1
  84. package/dist/value-object.d.ts +1 -1
  85. package/dist/value-object.d.ts.map +1 -1
  86. package/dist/value-object.js +7 -14
  87. package/dist/value-object.js.map +1 -1
  88. package/eslint.config.js +9 -3
  89. package/jest.config.js +1 -1
  90. package/package.json +14 -20
  91. package/src/base-entity.ts +3 -3
  92. package/src/criteria.ts +268 -87
  93. package/src/deep-proxy.ts +50 -38
  94. package/src/domain-event-bus.ts +152 -166
  95. package/src/domain-event.ts +53 -90
  96. package/src/entity.ts +16 -16
  97. package/src/exceptions.ts +435 -0
  98. package/src/id.ts +107 -94
  99. package/src/index.ts +32 -8
  100. package/src/paginated-result.ts +15 -3
  101. package/src/repository/index.ts +1 -6
  102. package/src/repository/unit-of-work.ts +1 -44
  103. package/src/types/criteria.ts +95 -17
  104. package/src/types/domain-event.ts +38 -0
  105. package/src/types/domain.ts +2 -3
  106. package/src/types/history-tracker.ts +1 -1
  107. package/src/types/index.ts +1 -0
  108. package/src/utils/criteria-operator-validation.ts +171 -0
  109. package/src/utils/helpers.ts +6 -0
  110. package/src/validation-error.ts +97 -97
  111. package/src/value-object.ts +3 -6
  112. package/tests/criteria.test.ts +324 -1
  113. package/tests/domain-events.test.ts +431 -445
  114. package/tests/entity-validation.test.ts +1 -1
  115. package/tests/entity.test.ts +33 -33
  116. package/tests/repository.test.ts +4 -2
  117. package/tests/utils.ts +254 -151
  118. package/tests/value-object-validation.test.ts +0 -9
  119. package/tsconfig.json +2 -24
  120. package/.github/workflows/ci.yml +0 -40
  121. package/.husky/commit-msg +0 -1
  122. package/.husky/pre-commit +0 -1
  123. package/.vscode/settings.json +0 -3
  124. package/commitlint.config.js +0 -23
  125. package/dist/repository/in-memory-repository.d.ts +0 -50
  126. package/dist/repository/in-memory-repository.d.ts.map +0 -1
  127. package/dist/repository/in-memory-repository.js +0 -97
  128. package/dist/repository/in-memory-repository.js.map +0 -1
  129. package/src/repository/in-memory-repository.ts +0 -116
@@ -1,90 +1,53 @@
1
- // ============================================================================
2
- // Domain Events - Event-Driven Architecture Support
3
- // ============================================================================
4
-
5
- import { Id } from "./id";
6
-
7
- /**
8
- * Interface for all domain events
9
- */
10
- export interface IDomainEvent {
11
- /**
12
- * Unique identifier for this event occurrence
13
- */
14
- readonly eventId: string;
15
-
16
- /**
17
- * When the event occurred
18
- */
19
- readonly occurredOn: Date;
20
-
21
- /**
22
- * Name/type of the event (e.g., "UserCreated", "OrderPlaced")
23
- */
24
- readonly eventName: string;
25
-
26
- /**
27
- * ID of the aggregate that raised this event
28
- */
29
- readonly aggregateId: string;
30
- }
31
-
32
- /**
33
- * Base class for domain events
34
- */
35
- export abstract class DomainEvent implements IDomainEvent {
36
- public readonly eventId: string;
37
- public readonly occurredOn: Date;
38
- public readonly aggregateId: string;
39
-
40
- constructor(aggregateId: Id | string) {
41
- this.eventId = this.generateEventId();
42
- this.occurredOn = new Date();
43
- this.aggregateId = aggregateId instanceof Id ? aggregateId.value : aggregateId;
44
- }
45
-
46
- /**
47
- * Get the event name (defaults to class name)
48
- */
49
- get eventName(): string {
50
- return this.constructor.name;
51
- }
52
-
53
- private generateEventId(): string {
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 './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> {}
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
+ }