@woltz/rich-domain 1.9.0 → 1.9.2
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/dist/cjs/core/aggregate-changes.d.ts +14 -0
- package/dist/cjs/core/aggregate-changes.d.ts.map +1 -1
- package/dist/cjs/core/aggregate-changes.js +18 -0
- package/dist/cjs/core/aggregate-changes.js.map +1 -1
- package/dist/cjs/core/base-entity.d.ts +2 -0
- package/dist/cjs/core/base-entity.d.ts.map +1 -1
- package/dist/cjs/core/base-entity.js +39 -41
- package/dist/cjs/core/base-entity.js.map +1 -1
- package/dist/cjs/core/change-tracker.d.ts +8 -0
- package/dist/cjs/core/change-tracker.d.ts.map +1 -1
- package/dist/cjs/core/change-tracker.js +36 -6
- package/dist/cjs/core/change-tracker.js.map +1 -1
- package/dist/cjs/core/domain-event.d.ts +3 -0
- package/dist/cjs/core/domain-event.d.ts.map +1 -1
- package/dist/cjs/core/domain-event.js +8 -1
- package/dist/cjs/core/domain-event.js.map +1 -1
- package/dist/cjs/core/value-object.d.ts.map +1 -1
- package/dist/cjs/core/value-object.js +3 -5
- package/dist/cjs/core/value-object.js.map +1 -1
- package/dist/cjs/criteria.d.ts +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/repository/entity-schema-registry.d.ts +56 -3
- package/dist/cjs/repository/entity-schema-registry.d.ts.map +1 -1
- package/dist/cjs/repository/entity-schema-registry.js +61 -6
- package/dist/cjs/repository/entity-schema-registry.js.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +1 -0
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/outbox-store.d.ts +91 -0
- package/dist/cjs/types/outbox-store.d.ts.map +1 -0
- package/dist/cjs/types/outbox-store.js +3 -0
- package/dist/cjs/types/outbox-store.js.map +1 -0
- package/dist/cjs/utils/helpers.d.ts +1 -0
- package/dist/cjs/utils/helpers.d.ts.map +1 -1
- package/dist/cjs/utils/helpers.js +4 -0
- package/dist/cjs/utils/helpers.js.map +1 -1
- package/dist/esm/core/aggregate-changes.d.ts +14 -0
- package/dist/esm/core/aggregate-changes.d.ts.map +1 -1
- package/dist/esm/core/aggregate-changes.js +18 -0
- package/dist/esm/core/aggregate-changes.js.map +1 -1
- package/dist/esm/core/base-entity.d.ts +2 -0
- package/dist/esm/core/base-entity.d.ts.map +1 -1
- package/dist/esm/core/base-entity.js +37 -39
- package/dist/esm/core/base-entity.js.map +1 -1
- package/dist/esm/core/change-tracker.d.ts +8 -0
- package/dist/esm/core/change-tracker.d.ts.map +1 -1
- package/dist/esm/core/change-tracker.js +36 -6
- package/dist/esm/core/change-tracker.js.map +1 -1
- package/dist/esm/core/domain-event.d.ts +3 -0
- package/dist/esm/core/domain-event.d.ts.map +1 -1
- package/dist/esm/core/domain-event.js +5 -1
- package/dist/esm/core/domain-event.js.map +1 -1
- package/dist/esm/core/value-object.d.ts.map +1 -1
- package/dist/esm/core/value-object.js +1 -3
- package/dist/esm/core/value-object.js.map +1 -1
- package/dist/esm/criteria.d.ts +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/repository/entity-schema-registry.d.ts +56 -3
- package/dist/esm/repository/entity-schema-registry.d.ts.map +1 -1
- package/dist/esm/repository/entity-schema-registry.js +61 -6
- package/dist/esm/repository/entity-schema-registry.js.map +1 -1
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm/types/outbox-store.d.ts +91 -0
- package/dist/esm/types/outbox-store.d.ts.map +1 -0
- package/dist/esm/types/outbox-store.js +2 -0
- package/dist/esm/types/outbox-store.js.map +1 -0
- package/dist/esm/utils/helpers.d.ts +1 -0
- package/dist/esm/utils/helpers.d.ts.map +1 -1
- package/dist/esm/utils/helpers.js +3 -0
- package/dist/esm/utils/helpers.js.map +1 -1
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/core/aggregate-changes.d.ts +14 -0
- package/dist/types/core/aggregate-changes.d.ts.map +1 -1
- package/dist/types/core/base-entity.d.ts +2 -0
- package/dist/types/core/base-entity.d.ts.map +1 -1
- package/dist/types/core/change-tracker.d.ts +8 -0
- package/dist/types/core/change-tracker.d.ts.map +1 -1
- package/dist/types/core/domain-event.d.ts +3 -0
- package/dist/types/core/domain-event.d.ts.map +1 -1
- package/dist/types/core/value-object.d.ts.map +1 -1
- package/dist/types/criteria.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/repository/entity-schema-registry.d.ts +56 -3
- package/dist/types/repository/entity-schema-registry.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/types/outbox-store.d.ts +91 -0
- package/dist/types/types/outbox-store.d.ts.map +1 -0
- package/dist/types/utils/helpers.d.ts +1 -0
- package/dist/types/utils/helpers.d.ts.map +1 -1
- package/package.json +68 -67
- package/src/constants.ts +82 -0
- package/src/core/aggregate-changes.ts +466 -0
- package/src/core/base-aggregate.ts +76 -0
- package/src/core/base-entity.ts +552 -0
- package/src/core/change-tracker.ts +1327 -0
- package/src/core/domain-event.ts +41 -0
- package/src/core/entity-changes.ts +146 -0
- package/src/core/entity.ts +13 -0
- package/src/core/id.ts +124 -0
- package/src/core/index.ts +9 -0
- package/src/core/value-object.ts +179 -0
- package/src/criteria.ts +574 -0
- package/src/exceptions.ts +549 -0
- package/src/index.ts +74 -0
- package/src/repository/base-repository.ts +81 -0
- package/src/repository/entity-schema-registry.ts +620 -0
- package/src/repository/index.ts +5 -0
- package/src/repository/mapper.ts +7 -0
- package/src/repository/paginated-result.ts +251 -0
- package/src/repository/unit-of-work.ts +76 -0
- package/src/types/change-tracker.ts +268 -0
- package/src/types/criteria.ts +197 -0
- package/src/types/domain-event.ts +29 -0
- package/src/types/domain.ts +41 -0
- package/src/types/event-bus.ts +17 -0
- package/src/types/index.ts +9 -0
- package/src/types/outbox-store.ts +97 -0
- package/src/types/standard-schema.ts +19 -0
- package/src/types/unit-of-work.ts +46 -0
- package/src/types/utils.ts +24 -0
- package/src/utils/criteria-operator-validation.ts +209 -0
- package/src/utils/crypto.ts +31 -0
- package/src/utils/helpers.ts +50 -0
- package/src/validation-error.ts +219 -0
- package/dist/cjs/t.d.ts +0 -2
- package/dist/cjs/t.d.ts.map +0 -1
- package/dist/cjs/t.js +0 -96
- package/dist/cjs/t.js.map +0 -1
- package/dist/esm/t.d.ts +0 -2
- package/dist/esm/t.d.ts.map +0 -1
- package/dist/esm/t.js +0 -94
- package/dist/esm/t.js.map +0 -1
- package/dist/types/t.d.ts +0 -2
- package/dist/types/t.d.ts.map +0 -1
|
@@ -0,0 +1,549 @@
|
|
|
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
|
+
if (Error.captureStackTrace) {
|
|
16
|
+
Error.captureStackTrace(this, this.constructor);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if an error is a DomainException
|
|
22
|
+
*/
|
|
23
|
+
static isDomainException(error: unknown): error is DomainException {
|
|
24
|
+
return (
|
|
25
|
+
error instanceof DomainException ||
|
|
26
|
+
(error instanceof Error &&
|
|
27
|
+
"__isDomainException" in error &&
|
|
28
|
+
(error as any).__isDomainException === true)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Convert to JSON for serialization
|
|
34
|
+
*/
|
|
35
|
+
toJSON(): {
|
|
36
|
+
name: string;
|
|
37
|
+
message: string;
|
|
38
|
+
code: string;
|
|
39
|
+
timestamp: string;
|
|
40
|
+
} {
|
|
41
|
+
return {
|
|
42
|
+
name: this.name,
|
|
43
|
+
message: this.message,
|
|
44
|
+
code: this.code,
|
|
45
|
+
timestamp: this.timestamp.toISOString(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Thrown when a domain rule or business logic is violated
|
|
52
|
+
*/
|
|
53
|
+
export class DomainError extends DomainException {
|
|
54
|
+
constructor(message: string, code?: string) {
|
|
55
|
+
super(message, code || "DOMAIN_ERROR");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Thrown for general application errors that don't fit other domain exceptions.
|
|
61
|
+
*/
|
|
62
|
+
export class ApplicationError extends DomainException {
|
|
63
|
+
constructor(message: string, code?: string) {
|
|
64
|
+
super(message, code || "APPLICATION_ERROR");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Thrown when authentication is required but not provided or invalid
|
|
70
|
+
*/
|
|
71
|
+
export class UnauthorizedError extends DomainException {
|
|
72
|
+
public readonly resource?: string;
|
|
73
|
+
public readonly action?: string;
|
|
74
|
+
|
|
75
|
+
constructor(message?: string, resource?: string, action?: string) {
|
|
76
|
+
const defaultMessage =
|
|
77
|
+
message || "Authentication required to access this resource";
|
|
78
|
+
|
|
79
|
+
super(defaultMessage, "UNAUTHORIZED");
|
|
80
|
+
this.resource = resource;
|
|
81
|
+
this.action = action;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
toJSON() {
|
|
85
|
+
return {
|
|
86
|
+
...super.toJSON(),
|
|
87
|
+
resource: this.resource,
|
|
88
|
+
action: this.action,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Thrown when the user is authenticated but doesn't have permission to perform an action
|
|
95
|
+
*/
|
|
96
|
+
export class ForbiddenError extends DomainException {
|
|
97
|
+
public readonly resource?: string;
|
|
98
|
+
public readonly action?: string;
|
|
99
|
+
public readonly userId?: string;
|
|
100
|
+
|
|
101
|
+
constructor(
|
|
102
|
+
message?: string,
|
|
103
|
+
resource?: string,
|
|
104
|
+
action?: string,
|
|
105
|
+
userId?: string
|
|
106
|
+
) {
|
|
107
|
+
const defaultMessage =
|
|
108
|
+
message || "You don't have permission to perform this action";
|
|
109
|
+
|
|
110
|
+
super(defaultMessage, "FORBIDDEN");
|
|
111
|
+
this.resource = resource;
|
|
112
|
+
this.action = action;
|
|
113
|
+
this.userId = userId;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
toJSON() {
|
|
117
|
+
return {
|
|
118
|
+
...super.toJSON(),
|
|
119
|
+
resource: this.resource,
|
|
120
|
+
action: this.action,
|
|
121
|
+
userId: this.userId,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Thrown when a request is malformed or contains invalid data
|
|
128
|
+
*/
|
|
129
|
+
export class BadRequestError extends DomainException {
|
|
130
|
+
public readonly field?: string;
|
|
131
|
+
public readonly reason?: string;
|
|
132
|
+
|
|
133
|
+
constructor(message: string, field?: string, reason?: string) {
|
|
134
|
+
super(message, "BAD_REQUEST");
|
|
135
|
+
this.field = field;
|
|
136
|
+
this.reason = reason;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
toJSON() {
|
|
140
|
+
return {
|
|
141
|
+
...super.toJSON(),
|
|
142
|
+
field: this.field,
|
|
143
|
+
reason: this.reason,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Thrown when an operation exceeds the allowed time limit
|
|
150
|
+
*/
|
|
151
|
+
export class TimeoutError extends DomainException {
|
|
152
|
+
public readonly operation: string;
|
|
153
|
+
public readonly timeoutMs?: number;
|
|
154
|
+
|
|
155
|
+
constructor(operation: string, timeoutMs?: number, message?: string) {
|
|
156
|
+
const defaultMessage = `Operation '${operation}' timed out${
|
|
157
|
+
timeoutMs ? ` after ${timeoutMs}ms` : ""
|
|
158
|
+
}`;
|
|
159
|
+
|
|
160
|
+
super(message || defaultMessage, "TIMEOUT_ERROR");
|
|
161
|
+
this.operation = operation;
|
|
162
|
+
this.timeoutMs = timeoutMs;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
toJSON() {
|
|
166
|
+
return {
|
|
167
|
+
...super.toJSON(),
|
|
168
|
+
operation: this.operation,
|
|
169
|
+
timeoutMs: this.timeoutMs,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Thrown when rate limit is exceeded
|
|
176
|
+
*/
|
|
177
|
+
export class RateLimitError extends DomainException {
|
|
178
|
+
public readonly limit: number;
|
|
179
|
+
public readonly windowMs: number;
|
|
180
|
+
public readonly retryAfter?: number;
|
|
181
|
+
|
|
182
|
+
constructor(
|
|
183
|
+
limit: number,
|
|
184
|
+
windowMs: number,
|
|
185
|
+
retryAfter?: number,
|
|
186
|
+
message?: string
|
|
187
|
+
) {
|
|
188
|
+
const defaultMessage = `Rate limit exceeded: ${limit} requests per ${windowMs}ms`;
|
|
189
|
+
|
|
190
|
+
super(message || defaultMessage, "RATE_LIMIT_ERROR");
|
|
191
|
+
this.limit = limit;
|
|
192
|
+
this.windowMs = windowMs;
|
|
193
|
+
this.retryAfter = retryAfter;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
toJSON() {
|
|
197
|
+
return {
|
|
198
|
+
...super.toJSON(),
|
|
199
|
+
limit: this.limit,
|
|
200
|
+
windowMs: this.windowMs,
|
|
201
|
+
retryAfter: this.retryAfter,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Thrown when an entity or aggregate is not found
|
|
208
|
+
*/
|
|
209
|
+
export class EntityNotFoundError extends DomainException {
|
|
210
|
+
public readonly entityType: string;
|
|
211
|
+
public readonly entityId?: string;
|
|
212
|
+
|
|
213
|
+
constructor(entityType: string, entityId?: string, message?: string) {
|
|
214
|
+
const defaultMessage = entityId
|
|
215
|
+
? `${entityType} with id '${entityId}' not found`
|
|
216
|
+
: `${entityType} not found`;
|
|
217
|
+
|
|
218
|
+
super(message || defaultMessage, "ENTITY_NOT_FOUND");
|
|
219
|
+
this.entityType = entityType;
|
|
220
|
+
this.entityId = entityId;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
toJSON() {
|
|
224
|
+
return {
|
|
225
|
+
...super.toJSON(),
|
|
226
|
+
entityType: this.entityType,
|
|
227
|
+
entityId: this.entityId,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Thrown when trying to create an entity that already exists
|
|
234
|
+
*/
|
|
235
|
+
export class EntityAlreadyExistsError extends DomainException {
|
|
236
|
+
public readonly entityType: string;
|
|
237
|
+
public readonly entityId?: string;
|
|
238
|
+
|
|
239
|
+
constructor(entityType: string, entityId?: string, message?: string) {
|
|
240
|
+
const defaultMessage = entityId
|
|
241
|
+
? `${entityType} with id '${entityId}' already exists`
|
|
242
|
+
: `${entityType} already exists`;
|
|
243
|
+
|
|
244
|
+
super(message || defaultMessage, "ENTITY_ALREADY_EXISTS");
|
|
245
|
+
this.entityType = entityType;
|
|
246
|
+
this.entityId = entityId;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
toJSON() {
|
|
250
|
+
return {
|
|
251
|
+
...super.toJSON(),
|
|
252
|
+
entityType: this.entityType,
|
|
253
|
+
entityId: this.entityId,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Base exception for repository operations
|
|
260
|
+
*/
|
|
261
|
+
export class RepositoryError extends DomainException {
|
|
262
|
+
constructor(message: string, code?: string) {
|
|
263
|
+
super(message, code || "REPOSITORY_ERROR");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Thrown when a persistence operation fails
|
|
269
|
+
*/
|
|
270
|
+
export class PersistenceError extends RepositoryError {
|
|
271
|
+
public readonly operation: string;
|
|
272
|
+
public readonly cause?: Error;
|
|
273
|
+
|
|
274
|
+
constructor(operation: string, message?: string, cause?: Error) {
|
|
275
|
+
const defaultMessage = `Persistence operation '${operation}' failed${
|
|
276
|
+
message ? `: ${message}` : ""
|
|
277
|
+
}`;
|
|
278
|
+
|
|
279
|
+
super(defaultMessage, "PERSISTENCE_ERROR");
|
|
280
|
+
this.operation = operation;
|
|
281
|
+
this.cause = cause;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
toJSON() {
|
|
285
|
+
return {
|
|
286
|
+
...super.toJSON(),
|
|
287
|
+
operation: this.operation,
|
|
288
|
+
cause: this.cause?.message,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Thrown when a concurrency conflict occurs (optimistic locking)
|
|
295
|
+
*/
|
|
296
|
+
export class ConcurrencyError extends RepositoryError {
|
|
297
|
+
public readonly entityType: string;
|
|
298
|
+
public readonly entityId: string;
|
|
299
|
+
|
|
300
|
+
constructor(entityType: string, entityId: string, message?: string) {
|
|
301
|
+
const defaultMessage =
|
|
302
|
+
message ||
|
|
303
|
+
`Concurrency conflict detected for ${entityType} with id '${entityId}'`;
|
|
304
|
+
|
|
305
|
+
super(defaultMessage, "CONCURRENCY_ERROR");
|
|
306
|
+
this.entityType = entityType;
|
|
307
|
+
this.entityId = entityId;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
toJSON() {
|
|
311
|
+
return {
|
|
312
|
+
...super.toJSON(),
|
|
313
|
+
entityType: this.entityType,
|
|
314
|
+
entityId: this.entityId,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Thrown when a database constraint is violated
|
|
321
|
+
*/
|
|
322
|
+
export class ConstraintViolationError extends RepositoryError {
|
|
323
|
+
public readonly constraint: string;
|
|
324
|
+
|
|
325
|
+
constructor(constraint: string, message?: string) {
|
|
326
|
+
const defaultMessage =
|
|
327
|
+
message || `Database constraint '${constraint}' violated`;
|
|
328
|
+
|
|
329
|
+
super(defaultMessage, "CONSTRAINT_VIOLATION");
|
|
330
|
+
this.constraint = constraint;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
toJSON() {
|
|
334
|
+
return {
|
|
335
|
+
...super.toJSON(),
|
|
336
|
+
constraint: this.constraint,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Thrown when a value object has invalid data
|
|
343
|
+
*/
|
|
344
|
+
export class InvalidValueObjectError extends DomainException {
|
|
345
|
+
public readonly valueObjectType: string;
|
|
346
|
+
public readonly invalidValue?: any;
|
|
347
|
+
|
|
348
|
+
constructor(valueObjectType: string, message: string, invalidValue?: any) {
|
|
349
|
+
super(message, "INVALID_VALUE_OBJECT");
|
|
350
|
+
this.valueObjectType = valueObjectType;
|
|
351
|
+
this.invalidValue = invalidValue;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
toJSON() {
|
|
355
|
+
return {
|
|
356
|
+
...super.toJSON(),
|
|
357
|
+
valueObjectType: this.valueObjectType,
|
|
358
|
+
invalidValue: this.invalidValue,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Thrown when a domain event operation fails
|
|
365
|
+
*/
|
|
366
|
+
export class DomainEventError extends DomainException {
|
|
367
|
+
public readonly eventType?: string;
|
|
368
|
+
|
|
369
|
+
constructor(message: string, eventType?: string) {
|
|
370
|
+
super(message, "DOMAIN_EVENT_ERROR");
|
|
371
|
+
this.eventType = eventType;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
toJSON() {
|
|
375
|
+
return {
|
|
376
|
+
...super.toJSON(),
|
|
377
|
+
eventType: this.eventType,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Thrown when an event handler fails
|
|
384
|
+
*/
|
|
385
|
+
export class EventHandlerError extends DomainEventError {
|
|
386
|
+
public readonly handlerName: string;
|
|
387
|
+
public readonly cause?: Error;
|
|
388
|
+
|
|
389
|
+
constructor(handlerName: string, eventType: string, cause?: Error) {
|
|
390
|
+
const message = `Event handler '${handlerName}' failed for event '${eventType}'${
|
|
391
|
+
cause ? `: ${cause.message}` : ""
|
|
392
|
+
}`;
|
|
393
|
+
|
|
394
|
+
super(message, eventType);
|
|
395
|
+
this.handlerName = handlerName;
|
|
396
|
+
this.cause = cause;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
toJSON() {
|
|
400
|
+
return {
|
|
401
|
+
...super.toJSON(),
|
|
402
|
+
handlerName: this.handlerName,
|
|
403
|
+
cause: this.cause?.message,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Thrown when a criteria or query is invalid
|
|
410
|
+
*/
|
|
411
|
+
export class InvalidCriteriaError extends DomainException {
|
|
412
|
+
public readonly field?: string;
|
|
413
|
+
|
|
414
|
+
constructor(message: string, field?: string) {
|
|
415
|
+
super(message, "INVALID_CRITERIA");
|
|
416
|
+
this.field = field;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
toJSON() {
|
|
420
|
+
return {
|
|
421
|
+
...super.toJSON(),
|
|
422
|
+
field: this.field,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Thrown when a transaction operation fails
|
|
429
|
+
*/
|
|
430
|
+
export class TransactionError extends DomainException {
|
|
431
|
+
public readonly operation: string;
|
|
432
|
+
public readonly cause?: Error;
|
|
433
|
+
|
|
434
|
+
constructor(operation: string, message?: string, cause?: Error) {
|
|
435
|
+
const defaultMessage = `Transaction ${operation} failed${
|
|
436
|
+
message ? `: ${message}` : ""
|
|
437
|
+
}`;
|
|
438
|
+
|
|
439
|
+
super(defaultMessage, "TRANSACTION_ERROR");
|
|
440
|
+
this.operation = operation;
|
|
441
|
+
this.cause = cause;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
toJSON() {
|
|
445
|
+
return {
|
|
446
|
+
...super.toJSON(),
|
|
447
|
+
operation: this.operation,
|
|
448
|
+
cause: this.cause?.message,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Thrown when an unexpected or unknown error occurs
|
|
455
|
+
*/
|
|
456
|
+
export class UnknownError extends DomainException {
|
|
457
|
+
public readonly originalError?: Error;
|
|
458
|
+
|
|
459
|
+
constructor(message?: string, originalError?: Error) {
|
|
460
|
+
const defaultMessage =
|
|
461
|
+
message || originalError?.message || "An unknown error occurred";
|
|
462
|
+
|
|
463
|
+
super(defaultMessage, "UNKNOWN_ERROR");
|
|
464
|
+
this.originalError = originalError;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
toJSON() {
|
|
468
|
+
return {
|
|
469
|
+
...super.toJSON(),
|
|
470
|
+
originalError: this.originalError?.message,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Thrown when a feature is not implemented yet
|
|
477
|
+
*/
|
|
478
|
+
export class NotImplementedError extends DomainException {
|
|
479
|
+
public readonly feature: string;
|
|
480
|
+
|
|
481
|
+
constructor(feature: string, message?: string) {
|
|
482
|
+
const defaultMessage = message || `Feature '${feature}' is not implemented`;
|
|
483
|
+
|
|
484
|
+
super(defaultMessage, "NOT_IMPLEMENTED");
|
|
485
|
+
this.feature = feature;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
toJSON() {
|
|
489
|
+
return {
|
|
490
|
+
...super.toJSON(),
|
|
491
|
+
feature: this.feature,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Thrown when a required configuration is missing
|
|
498
|
+
*/
|
|
499
|
+
export class ConfigurationError extends DomainException {
|
|
500
|
+
public readonly configKey?: string;
|
|
501
|
+
|
|
502
|
+
constructor(message: string, configKey?: string) {
|
|
503
|
+
super(message, "CONFIGURATION_ERROR");
|
|
504
|
+
this.configKey = configKey;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
toJSON() {
|
|
508
|
+
return {
|
|
509
|
+
...super.toJSON(),
|
|
510
|
+
configKey: this.configKey,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Thrown when mapping between domain and persistence fails
|
|
517
|
+
*/
|
|
518
|
+
export class MapperError extends DomainException {
|
|
519
|
+
public readonly direction: "toDomain" | "toPersistence";
|
|
520
|
+
public readonly entityType: string;
|
|
521
|
+
public readonly cause?: Error;
|
|
522
|
+
|
|
523
|
+
constructor(
|
|
524
|
+
direction: "toDomain" | "toPersistence",
|
|
525
|
+
entityType: string,
|
|
526
|
+
message?: string,
|
|
527
|
+
cause?: Error
|
|
528
|
+
) {
|
|
529
|
+
const defaultMessage =
|
|
530
|
+
message ||
|
|
531
|
+
`Failed to map ${entityType} ${
|
|
532
|
+
direction === "toDomain" ? "to domain" : "to persistence"
|
|
533
|
+
}`;
|
|
534
|
+
|
|
535
|
+
super(defaultMessage, "MAPPER_ERROR");
|
|
536
|
+
this.direction = direction;
|
|
537
|
+
this.entityType = entityType;
|
|
538
|
+
this.cause = cause;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
toJSON() {
|
|
542
|
+
return {
|
|
543
|
+
...super.toJSON(),
|
|
544
|
+
direction: this.direction,
|
|
545
|
+
entityType: this.entityType,
|
|
546
|
+
cause: this.cause?.message,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export * from "./validation-error.js";
|
|
2
|
+
export * from "./core/domain-event.js";
|
|
3
|
+
export * from "./exceptions.js";
|
|
4
|
+
export * from "./criteria.js";
|
|
5
|
+
export { isValidOperatorForType } from "./utils/criteria-operator-validation.js";
|
|
6
|
+
export {
|
|
7
|
+
ARRAY_OPERATORS,
|
|
8
|
+
BOOLEAN_OPERATORS,
|
|
9
|
+
DATE_OPERATORS,
|
|
10
|
+
NUMBER_OPERATORS,
|
|
11
|
+
STRING_OPERATORS,
|
|
12
|
+
FILTER_OPERATORS,
|
|
13
|
+
} from "./constants.js";
|
|
14
|
+
export {
|
|
15
|
+
Id,
|
|
16
|
+
Entity,
|
|
17
|
+
Aggregate,
|
|
18
|
+
ValueObject,
|
|
19
|
+
AggregateChanges,
|
|
20
|
+
DomainEvent,
|
|
21
|
+
} from "./core/index.js";
|
|
22
|
+
export {
|
|
23
|
+
type PaginatedJsonResult,
|
|
24
|
+
type CollectionConfig,
|
|
25
|
+
Repository,
|
|
26
|
+
Mapper,
|
|
27
|
+
UnitOfWork,
|
|
28
|
+
PaginatedResult,
|
|
29
|
+
ReadRepository,
|
|
30
|
+
WriteAndRead,
|
|
31
|
+
WriteRepository,
|
|
32
|
+
BaseTransactionContext,
|
|
33
|
+
EntitySchemaRegistry,
|
|
34
|
+
} from "./repository/index.js";
|
|
35
|
+
export type {
|
|
36
|
+
EntityHooks,
|
|
37
|
+
Filter,
|
|
38
|
+
EntityValidation,
|
|
39
|
+
IDomainEvent,
|
|
40
|
+
VOValidation,
|
|
41
|
+
VOHooks,
|
|
42
|
+
ValidationConfig,
|
|
43
|
+
Primitive,
|
|
44
|
+
TransactionContext,
|
|
45
|
+
PaginationMeta,
|
|
46
|
+
Pagination,
|
|
47
|
+
OrderDirection,
|
|
48
|
+
Order,
|
|
49
|
+
IUnitOfWork,
|
|
50
|
+
FieldPath,
|
|
51
|
+
FilterOperator,
|
|
52
|
+
Search,
|
|
53
|
+
FilterValueFor,
|
|
54
|
+
PathValue,
|
|
55
|
+
OperatorsForType,
|
|
56
|
+
DateOperators,
|
|
57
|
+
NumberOperators,
|
|
58
|
+
BatchDeleteOperation,
|
|
59
|
+
BatchCreateOperation,
|
|
60
|
+
BatchUpdateOperation,
|
|
61
|
+
BatchOperations,
|
|
62
|
+
BatchCreateItem,
|
|
63
|
+
BatchUpdateItem,
|
|
64
|
+
BatchDeleteItem,
|
|
65
|
+
StringOperators,
|
|
66
|
+
BooleanOperators,
|
|
67
|
+
ArrayOperators,
|
|
68
|
+
CriteriaOptions,
|
|
69
|
+
IDomainEventBus,
|
|
70
|
+
IOutboxStore,
|
|
71
|
+
OutboxEntryData,
|
|
72
|
+
OutboxFetchResult,
|
|
73
|
+
OutboxStatus,
|
|
74
|
+
} from "./types/index.js";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Aggregate } from "../core/entity.js";
|
|
2
|
+
import type { Criteria } from "../criteria.js";
|
|
3
|
+
import { PaginatedResult } from "./paginated-result.js";
|
|
4
|
+
import { Mapper } from "./mapper.js";
|
|
5
|
+
|
|
6
|
+
export abstract class ReadRepository<Agg extends Aggregate<any>> {
|
|
7
|
+
/**
|
|
8
|
+
* Find entities based on criteria.
|
|
9
|
+
* @param criteria - The criteria to use for the search. If not provided, all entities will be returned. (optional)
|
|
10
|
+
* @returns A promise that resolves to a paginated result of entities.
|
|
11
|
+
*/
|
|
12
|
+
abstract find(criteria?: Criteria<Agg>): Promise<PaginatedResult<Agg>>;
|
|
13
|
+
abstract findById(id: string): Promise<Agg | null>;
|
|
14
|
+
abstract findManyByIds(ids: string[]): Promise<Agg[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Count the number of entities based on criteria.
|
|
17
|
+
* @param criteria - The criteria to use for the count. If not provided, all entities will be counted. (optional)
|
|
18
|
+
* @returns A promise that resolves to the number of entities.
|
|
19
|
+
*/
|
|
20
|
+
abstract count(criteria?: Criteria<Agg>): Promise<number>;
|
|
21
|
+
/**
|
|
22
|
+
* Check if an entity exists based on its identifier.
|
|
23
|
+
* @param id - The identifier of the entity to check.
|
|
24
|
+
* @returns A promise that resolves to a boolean.
|
|
25
|
+
*/
|
|
26
|
+
abstract exists(id: string): Promise<boolean>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export abstract class WriteRepository<Agg extends Aggregate<any>> {
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* Save or update an entity.
|
|
33
|
+
* @param entity - The entity to save.
|
|
34
|
+
* @returns void
|
|
35
|
+
*/
|
|
36
|
+
abstract save(entity: Agg): Promise<void>;
|
|
37
|
+
abstract delete(entity: Agg): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export abstract class WriteAndRead<Agg extends Aggregate<any>> {
|
|
41
|
+
/**
|
|
42
|
+
* Find entities based on criteria.
|
|
43
|
+
* @param criteria - The criteria to use for the search. If not provided, all entities will be returned. (optional)
|
|
44
|
+
* @returns A promise that resolves to a paginated result of entities.
|
|
45
|
+
*/
|
|
46
|
+
abstract find(criteria?: Criteria<Agg>): Promise<PaginatedResult<Agg>>;
|
|
47
|
+
abstract findById(id: string): Promise<Agg | null>;
|
|
48
|
+
abstract findManyByIds(ids: string[]): Promise<Agg[]>;
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* Save or update an entity.
|
|
52
|
+
* @param entity - The entity to save.
|
|
53
|
+
* @returns void
|
|
54
|
+
*/
|
|
55
|
+
abstract save(entity: Agg): Promise<void>;
|
|
56
|
+
abstract delete(entity: Agg): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Count the number of entities based on criteria.
|
|
59
|
+
* @param criteria - The criteria to use for the count. If not provided, all entities will be counted. (optional)
|
|
60
|
+
* @returns A promise that resolves to the number of entities.
|
|
61
|
+
*/
|
|
62
|
+
abstract count(criteria?: Criteria<Agg>): Promise<number>;
|
|
63
|
+
/**
|
|
64
|
+
* Check if an entity exists based on its identifier.
|
|
65
|
+
* @param id - The identifier of the entity to check.
|
|
66
|
+
* @returns A promise that resolves to a boolean.
|
|
67
|
+
*/
|
|
68
|
+
abstract exists(id: string): Promise<boolean>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export abstract class Repository<
|
|
72
|
+
TDomain extends Aggregate<any>,
|
|
73
|
+
> extends WriteAndRead<TDomain> {
|
|
74
|
+
protected abstract readonly toDomainMapper: Mapper<unknown, TDomain>;
|
|
75
|
+
protected abstract readonly toPersistenceMapper: Mapper<TDomain, unknown>;
|
|
76
|
+
/**
|
|
77
|
+
* Provide the model name of the repository. Usually the table name in the database.
|
|
78
|
+
* @returns The model name of the repository.
|
|
79
|
+
*/
|
|
80
|
+
protected abstract get model(): any;
|
|
81
|
+
}
|