@rineex/ddd 2.1.0 → 2.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/dist/index.d.mts +726 -395
- package/dist/index.d.ts +726 -395
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,211 @@
|
|
|
1
|
+
import { Primitive as Primitive$2, Tagged } from 'type-fest';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* HTTP status code catalog.
|
|
6
|
+
*
|
|
7
|
+
* This object provides a typed, immutable map of standard HTTP status names
|
|
8
|
+
* to their numeric codes. Designed for server-side frameworks such as Fastify.
|
|
9
|
+
*
|
|
10
|
+
* - All identifiers use clear, canonical semantic names.
|
|
11
|
+
* - Values are numeric status codes.
|
|
12
|
+
* - Frozen to prevent runtime mutation.
|
|
13
|
+
* - Exporting `HttpStatus` ensures type-safe usage across the codebase.
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { HttpStatus } from 'path-to-this-file';
|
|
17
|
+
*
|
|
18
|
+
* function handleRequest() {
|
|
19
|
+
* return {
|
|
20
|
+
* statusCode: HttpStatus.OK,
|
|
21
|
+
* body: 'Success',
|
|
22
|
+
* };
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
declare const HttpStatus: Readonly<{
|
|
27
|
+
readonly REQUEST_HEADER_FIELDS_TOO_LARGE: 431;
|
|
28
|
+
readonly NETWORK_AUTHENTICATION_REQUIRED: 511;
|
|
29
|
+
readonly NON_AUTHORITATIVE_INFORMATION: 203;
|
|
30
|
+
readonly PROXY_AUTHENTICATION_REQUIRED: 407;
|
|
31
|
+
readonly UNAVAILABLE_FOR_LEGAL_REASONS: 451;
|
|
32
|
+
readonly HTTP_VERSION_NOT_SUPPORTED: 505;
|
|
33
|
+
readonly BANDWIDTH_LIMIT_EXCEEDED: 509;
|
|
34
|
+
readonly VARIANT_ALSO_NEGOTIATES: 506;
|
|
35
|
+
readonly UNSUPPORTED_MEDIA_TYPE: 415;
|
|
36
|
+
readonly RANGE_NOT_SATISFIABLE: 416;
|
|
37
|
+
readonly PRECONDITION_REQUIRED: 428;
|
|
38
|
+
readonly INTERNAL_SERVER_ERROR: 500;
|
|
39
|
+
readonly UNPROCESSABLE_ENTITY: 422;
|
|
40
|
+
readonly INSUFFICIENT_STORAGE: 507;
|
|
41
|
+
readonly SWITCHING_PROTOCOLS: 101;
|
|
42
|
+
readonly PRECONDITION_FAILED: 412;
|
|
43
|
+
readonly MISDIRECTED_REQUEST: 421;
|
|
44
|
+
readonly SERVICE_UNAVAILABLE: 503;
|
|
45
|
+
readonly TEMPORARY_REDIRECT: 307;
|
|
46
|
+
readonly PERMANENT_REDIRECT: 308;
|
|
47
|
+
readonly METHOD_NOT_ALLOWED: 405;
|
|
48
|
+
readonly EXPECTATION_FAILED: 417;
|
|
49
|
+
readonly MOVED_PERMANENTLY: 301;
|
|
50
|
+
readonly PAYLOAD_TOO_LARGE: 413;
|
|
51
|
+
readonly FAILED_DEPENDENCY: 424;
|
|
52
|
+
readonly TOO_MANY_REQUESTS: 429;
|
|
53
|
+
readonly ALREADY_REPORTED: 208;
|
|
54
|
+
readonly MULTIPLE_CHOICES: 300;
|
|
55
|
+
readonly PAYMENT_REQUIRED: 402;
|
|
56
|
+
readonly UPGRADE_REQUIRED: 426;
|
|
57
|
+
readonly PARTIAL_CONTENT: 206;
|
|
58
|
+
readonly REQUEST_TIMEOUT: 408;
|
|
59
|
+
readonly LENGTH_REQUIRED: 411;
|
|
60
|
+
readonly NOT_IMPLEMENTED: 501;
|
|
61
|
+
readonly GATEWAY_TIMEOUT: 504;
|
|
62
|
+
readonly NOT_ACCEPTABLE: 406;
|
|
63
|
+
readonly RESET_CONTENT: 205;
|
|
64
|
+
readonly LOOP_DETECTED: 508;
|
|
65
|
+
readonly MULTI_STATUS: 207;
|
|
66
|
+
readonly NOT_MODIFIED: 304;
|
|
67
|
+
readonly UNAUTHORIZED: 401;
|
|
68
|
+
readonly URI_TOO_LONG: 414;
|
|
69
|
+
readonly NOT_EXTENDED: 510;
|
|
70
|
+
readonly EARLY_HINTS: 103;
|
|
71
|
+
readonly BAD_REQUEST: 400;
|
|
72
|
+
readonly IM_A_TEAPOT: 418;
|
|
73
|
+
readonly BAD_GATEWAY: 502;
|
|
74
|
+
readonly PROCESSING: 102;
|
|
75
|
+
readonly NO_CONTENT: 204;
|
|
76
|
+
readonly SEE_OTHER: 303;
|
|
77
|
+
readonly USE_PROXY: 305;
|
|
78
|
+
readonly FORBIDDEN: 403;
|
|
79
|
+
readonly NOT_FOUND: 404;
|
|
80
|
+
readonly TOO_EARLY: 425;
|
|
81
|
+
readonly CONTINUE: 100;
|
|
82
|
+
readonly ACCEPTED: 202;
|
|
83
|
+
readonly CONFLICT: 409;
|
|
84
|
+
readonly CREATED: 201;
|
|
85
|
+
readonly IM_USED: 226;
|
|
86
|
+
readonly LOCKED: 423;
|
|
87
|
+
readonly FOUND: 302;
|
|
88
|
+
readonly GONE: 410;
|
|
89
|
+
readonly OK: 200;
|
|
90
|
+
}>;
|
|
91
|
+
type HttpStatusCode = keyof typeof HttpStatusMessage;
|
|
92
|
+
/**
|
|
93
|
+
* HTTP status messages mapped by numeric code.
|
|
94
|
+
* Use for sending descriptive text in responses or logging.
|
|
95
|
+
*/
|
|
96
|
+
declare const HttpStatusMessage: {
|
|
97
|
+
readonly 431: "Request Header Fields Too Large";
|
|
98
|
+
readonly 511: "Network Authentication Required";
|
|
99
|
+
readonly 203: "Non-Authoritative Information";
|
|
100
|
+
readonly 407: "Proxy Authentication Required";
|
|
101
|
+
readonly 451: "Unavailable For Legal Reasons";
|
|
102
|
+
readonly 505: "HTTP Version Not Supported";
|
|
103
|
+
readonly 509: "Bandwidth Limit Exceeded";
|
|
104
|
+
readonly 506: "Variant Also Negotiates";
|
|
105
|
+
readonly 415: "Unsupported Media Type";
|
|
106
|
+
readonly 416: "Range Not Satisfiable";
|
|
107
|
+
readonly 428: "Precondition Required";
|
|
108
|
+
readonly 500: "Internal Server Error";
|
|
109
|
+
readonly 422: "Unprocessable Entity";
|
|
110
|
+
readonly 507: "Insufficient Storage";
|
|
111
|
+
readonly 101: "Switching Protocols";
|
|
112
|
+
readonly 412: "Precondition Failed";
|
|
113
|
+
readonly 421: "Misdirected Request";
|
|
114
|
+
readonly 503: "Service Unavailable";
|
|
115
|
+
readonly 307: "Temporary Redirect";
|
|
116
|
+
readonly 308: "Permanent Redirect";
|
|
117
|
+
readonly 405: "Method Not Allowed";
|
|
118
|
+
readonly 417: "Expectation Failed";
|
|
119
|
+
readonly 301: "Moved Permanently";
|
|
120
|
+
readonly 413: "Payload Too Large";
|
|
121
|
+
readonly 424: "Failed Dependency";
|
|
122
|
+
readonly 429: "Too Many Requests";
|
|
123
|
+
readonly 208: "Already Reported";
|
|
124
|
+
readonly 300: "Multiple Choices";
|
|
125
|
+
readonly 402: "Payment Required";
|
|
126
|
+
readonly 426: "Upgrade Required";
|
|
127
|
+
readonly 206: "Partial Content";
|
|
128
|
+
readonly 408: "Request Timeout";
|
|
129
|
+
readonly 411: "Length Required";
|
|
130
|
+
readonly 501: "Not Implemented";
|
|
131
|
+
readonly 504: "Gateway Timeout";
|
|
132
|
+
readonly 406: "Not Acceptable";
|
|
133
|
+
readonly 205: "Reset Content";
|
|
134
|
+
readonly 508: "Loop Detected";
|
|
135
|
+
readonly 207: "Multi-Status";
|
|
136
|
+
readonly 304: "Not Modified";
|
|
137
|
+
readonly 401: "Unauthorized";
|
|
138
|
+
readonly 414: "URI Too Long";
|
|
139
|
+
readonly 418: "I'm a Teapot";
|
|
140
|
+
readonly 510: "Not Extended";
|
|
141
|
+
readonly 103: "Early Hints";
|
|
142
|
+
readonly 400: "Bad Request";
|
|
143
|
+
readonly 502: "Bad Gateway";
|
|
144
|
+
readonly 102: "Processing";
|
|
145
|
+
readonly 204: "No Content";
|
|
146
|
+
readonly 303: "See Other";
|
|
147
|
+
readonly 305: "Use Proxy";
|
|
148
|
+
readonly 403: "Forbidden";
|
|
149
|
+
readonly 404: "Not Found";
|
|
150
|
+
readonly 425: "Too Early";
|
|
151
|
+
readonly 100: "Continue";
|
|
152
|
+
readonly 202: "Accepted";
|
|
153
|
+
readonly 226: "I'm Used";
|
|
154
|
+
readonly 409: "Conflict";
|
|
155
|
+
readonly 201: "Created";
|
|
156
|
+
readonly 423: "Locked";
|
|
157
|
+
readonly 302: "Found";
|
|
158
|
+
readonly 410: "Gone";
|
|
159
|
+
readonly 200: "OK";
|
|
160
|
+
};
|
|
161
|
+
type HttpStatusMessage = (typeof HttpStatusMessage)[keyof typeof HttpStatusMessage];
|
|
162
|
+
|
|
163
|
+
interface ErrorParams {
|
|
164
|
+
message: string;
|
|
165
|
+
code: HttpStatusMessage;
|
|
166
|
+
status?: HttpStatusCode;
|
|
167
|
+
metadata?: Record<string, unknown>;
|
|
168
|
+
isOperational?: boolean;
|
|
169
|
+
cause?: Error;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Abstract base class for application-level errors with structured error handling.
|
|
173
|
+
*
|
|
174
|
+
* Extends the native Error class to provide machine-readable error codes, HTTP status codes,
|
|
175
|
+
* operational error classification, and optional metadata for better error tracking and client communication.
|
|
176
|
+
*
|
|
177
|
+
* @abstract
|
|
178
|
+
* @extends {Error}
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* class UserNotFoundError extends ApplicationError {
|
|
183
|
+
* constructor(userId: string) {
|
|
184
|
+
* super({
|
|
185
|
+
* code: HttpStatusMessage['404'],
|
|
186
|
+
* status: 404,
|
|
187
|
+
* isOperational: true,
|
|
188
|
+
* message: `User with id ${userId} not found`,
|
|
189
|
+
* metadata: { userId }
|
|
190
|
+
* });
|
|
191
|
+
* }
|
|
192
|
+
* }
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
declare abstract class ApplicationError extends Error {
|
|
196
|
+
/** Optional cause (linked error) */
|
|
197
|
+
readonly cause?: Error | undefined;
|
|
198
|
+
/** Machine-readable error code (e.g. `OTP_LOCKED`, `USER_NOT_FOUND`) */
|
|
199
|
+
readonly code: HttpStatusMessage;
|
|
200
|
+
/** Operational vs programmer error flag */
|
|
201
|
+
readonly isOperational: boolean;
|
|
202
|
+
/** Optional structured metadata for debugging or clients */
|
|
203
|
+
readonly metadata?: Record<string, unknown> | undefined;
|
|
204
|
+
/** HTTP status code intended for response layer */
|
|
205
|
+
readonly status: HttpStatusCode;
|
|
206
|
+
constructor({ code, isOperational, status, metadata, message, cause, }: ErrorParams);
|
|
207
|
+
}
|
|
208
|
+
|
|
1
209
|
/**
|
|
2
210
|
* Port interface for application services that execute commands or queries.
|
|
3
211
|
*
|
|
@@ -33,11 +241,15 @@ interface ApplicationServicePort<I, O> {
|
|
|
33
241
|
* (e.g., UUID, ULID, or Database Sequence).
|
|
34
242
|
* @template T - The underlying primitive type of the ID (usually string or number).
|
|
35
243
|
*/
|
|
36
|
-
interface EntityId
|
|
37
|
-
equals: (other?:
|
|
244
|
+
interface EntityId {
|
|
245
|
+
equals: <T extends EntityId>(other?: T) => boolean;
|
|
38
246
|
toString: () => string;
|
|
247
|
+
readonly value: Readonly<Primitive$2>;
|
|
39
248
|
}
|
|
40
249
|
|
|
250
|
+
type Immutable<T> = T extends (...args: any[]) => any ? T : T extends Date ? T : T extends Map<infer K, infer V> ? ReadonlyMap<Immutable<K>, Immutable<V>> : T extends Set<infer U> ? ReadonlySet<Immutable<U>> : T extends object ? {
|
|
251
|
+
readonly [K in keyof T]: Immutable<T[K]>;
|
|
252
|
+
} : T;
|
|
41
253
|
/**
|
|
42
254
|
* Configuration for the base Entity constructor.
|
|
43
255
|
* Forces a single-object argument pattern to avoid positional argument errors.
|
|
@@ -48,7 +260,7 @@ interface EntityProps<ID extends EntityId, Props> {
|
|
|
48
260
|
readonly id: ID;
|
|
49
261
|
/** Optional creation timestamp; defaults to 'now' if not provided */
|
|
50
262
|
readonly createdAt?: Date;
|
|
51
|
-
|
|
263
|
+
props: Props;
|
|
52
264
|
}
|
|
53
265
|
/**
|
|
54
266
|
* Abstract Base Entity for Domain-Driven Design (DDD).
|
|
@@ -58,15 +270,21 @@ interface EntityProps<ID extends EntityId, Props> {
|
|
|
58
270
|
* @template ID - The specific Identity Value Object type.
|
|
59
271
|
*/
|
|
60
272
|
declare abstract class Entity<ID extends EntityId, Props> {
|
|
273
|
+
#private;
|
|
61
274
|
/** The timestamp when this entity was first instantiated/created */
|
|
62
275
|
readonly createdAt: Date;
|
|
63
276
|
/** The immutable unique identifier for this entity */
|
|
64
277
|
readonly id: ID;
|
|
278
|
+
/**
|
|
279
|
+
* Read-only view of entity state.
|
|
280
|
+
* External code can never mutate internal state.
|
|
281
|
+
*/
|
|
282
|
+
protected get props(): Immutable<Props>;
|
|
65
283
|
/**
|
|
66
284
|
* Protected constructor to be called by subclasses.
|
|
67
|
-
* @param
|
|
285
|
+
* @param params - Initial identity and metadata.
|
|
68
286
|
*/
|
|
69
|
-
protected constructor(
|
|
287
|
+
protected constructor(params: EntityProps<ID, Props>);
|
|
70
288
|
/**
|
|
71
289
|
* Compares entities by identity.
|
|
72
290
|
* In DDD, two entities are considered equal if their IDs match,
|
|
@@ -87,6 +305,7 @@ declare abstract class Entity<ID extends EntityId, Props> {
|
|
|
87
305
|
* @throws {Error} Should throw a specific DomainError if validation fails.
|
|
88
306
|
*/
|
|
89
307
|
abstract validate(): void;
|
|
308
|
+
protected mutate(updater: (current: Props) => Props): void;
|
|
90
309
|
}
|
|
91
310
|
|
|
92
311
|
type Primitive$1 = boolean | number | string | null;
|
|
@@ -95,13 +314,14 @@ type Serializable = Primitive$1 | Serializable[] | {
|
|
|
95
314
|
};
|
|
96
315
|
type DomainEventPayload = Record<string, Serializable>;
|
|
97
316
|
type UnixTimestampMillis = number;
|
|
98
|
-
type DomainEventProps<AggregateId extends EntityId
|
|
317
|
+
type DomainEventProps<Payload, AggregateId extends EntityId> = {
|
|
99
318
|
id?: string;
|
|
100
319
|
aggregateId: AggregateId;
|
|
101
320
|
schemaVersion: number;
|
|
102
321
|
occurredAt: UnixTimestampMillis;
|
|
103
322
|
payload: Payload;
|
|
104
323
|
};
|
|
324
|
+
type CreateEventProps<EventProps, ID extends EntityId> = DomainEventProps<EventProps, ID>;
|
|
105
325
|
declare abstract class DomainEvent<AggregateId extends EntityId = EntityId, T extends DomainEventPayload = DomainEventPayload> {
|
|
106
326
|
readonly aggregateId: AggregateId;
|
|
107
327
|
abstract readonly eventName: string;
|
|
@@ -109,7 +329,7 @@ declare abstract class DomainEvent<AggregateId extends EntityId = EntityId, T ex
|
|
|
109
329
|
readonly occurredAt: number;
|
|
110
330
|
readonly payload: Readonly<T>;
|
|
111
331
|
readonly schemaVersion: number;
|
|
112
|
-
protected constructor(props: DomainEventProps<
|
|
332
|
+
protected constructor(props: DomainEventProps<T, AggregateId>);
|
|
113
333
|
toPrimitives(): Readonly<{
|
|
114
334
|
id: string;
|
|
115
335
|
eventName: string;
|
|
@@ -120,36 +340,28 @@ declare abstract class DomainEvent<AggregateId extends EntityId = EntityId, T ex
|
|
|
120
340
|
}>;
|
|
121
341
|
}
|
|
122
342
|
|
|
123
|
-
/**
|
|
124
|
-
* Interface for AggregateRoot to ensure type safety and extensibility.
|
|
125
|
-
*/
|
|
126
|
-
interface Props<P> extends EntityProps<EntityId, P> {
|
|
127
|
-
readonly domainEvents: readonly DomainEvent[];
|
|
128
|
-
/**
|
|
129
|
-
* Adds a domain event to the aggregate.
|
|
130
|
-
* @param event The domain event to add.
|
|
131
|
-
*/
|
|
132
|
-
addEvent: (event: DomainEvent) => void;
|
|
133
|
-
/**
|
|
134
|
-
* Retrieves and clears all domain events recorded by this aggregate.
|
|
135
|
-
*
|
|
136
|
-
* Domain events represent facts that occurred as a result of state changes
|
|
137
|
-
* within the aggregate. This method transfers ownership of those events
|
|
138
|
-
* to the application layer for further processing (e.g. publishing).
|
|
139
|
-
*
|
|
140
|
-
* Calling this method has the side effect of clearing the aggregate's
|
|
141
|
-
* internal event collection to prevent duplicate handling.
|
|
142
|
-
*
|
|
143
|
-
* This method is intended to be invoked by application services
|
|
144
|
-
* after the aggregate has been successfully persisted.
|
|
145
|
-
*
|
|
146
|
-
* @returns A read-only list of domain events raised by this aggregate.
|
|
147
|
-
*/
|
|
148
|
-
pullDomainEvents: () => readonly DomainEvent[];
|
|
149
|
-
}
|
|
150
343
|
/**
|
|
151
344
|
* Base class for aggregate roots in DDD, encapsulating domain events and validation.
|
|
152
|
-
*
|
|
345
|
+
*
|
|
346
|
+
* Aggregate roots are entities that serve as entry points to aggregates. They:
|
|
347
|
+
* - Enforce invariants across the aggregate
|
|
348
|
+
* - Manage domain events
|
|
349
|
+
* - Define transaction boundaries
|
|
350
|
+
*
|
|
351
|
+
* @template ID - The type of the aggregate's identity (must extend EntityId)
|
|
352
|
+
* @template P - The type of the aggregate's properties
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```typescript
|
|
356
|
+
* interface UserProps {
|
|
357
|
+
* email: string;
|
|
358
|
+
* isActive: boolean;
|
|
359
|
+
* }
|
|
360
|
+
*
|
|
361
|
+
* class User extends AggregateRoot<AggregateId, UserProps> {
|
|
362
|
+
* // Implementation...
|
|
363
|
+
* }
|
|
364
|
+
* ```
|
|
153
365
|
*/
|
|
154
366
|
declare abstract class AggregateRoot<ID extends EntityId, P> extends Entity<ID, P> {
|
|
155
367
|
/**
|
|
@@ -169,48 +381,6 @@ declare abstract class AggregateRoot<ID extends EntityId, P> extends Entity<ID,
|
|
|
169
381
|
pullDomainEvents(): readonly DomainEvent[];
|
|
170
382
|
}
|
|
171
383
|
|
|
172
|
-
interface DomainErrorMetadata {
|
|
173
|
-
cause?: {
|
|
174
|
-
name: string;
|
|
175
|
-
message: string;
|
|
176
|
-
stack?: string;
|
|
177
|
-
};
|
|
178
|
-
[key: string]: unknown;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Base class for all Domain Errors in the application.
|
|
182
|
-
*
|
|
183
|
-
* This class ensures:
|
|
184
|
-
* 1. Serializable and structured for logs or API responses.
|
|
185
|
-
* 2. Identifiable via stable error codes (not just class names).
|
|
186
|
-
* 3. Contextual with optional structured metadata.
|
|
187
|
-
* 4. Supports error chaining (cause) and stack trace preservation.
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* export class InsufficientFundsError extends DomainError {
|
|
191
|
-
* constructor(accountId: string, currentBalance: number) {
|
|
192
|
-
* super(
|
|
193
|
-
* `Account ${accountId} has insufficient funds.`,
|
|
194
|
-
* 'INSUFFICIENT_FUNDS',
|
|
195
|
-
* { accountId, currentBalance }
|
|
196
|
-
* );
|
|
197
|
-
* }
|
|
198
|
-
* }
|
|
199
|
-
*/
|
|
200
|
-
declare abstract class DomainError$1 extends Error {
|
|
201
|
-
/** Stable, machine-readable error code */
|
|
202
|
-
readonly code: string;
|
|
203
|
-
/** Structured, immutable domain metadata */
|
|
204
|
-
readonly metadata: Readonly<DomainErrorMetadata>;
|
|
205
|
-
/**
|
|
206
|
-
* @param message - Human-readable error message
|
|
207
|
-
* @param code - Stable error code
|
|
208
|
-
* @param metadata - Domain-specific structured data; optional `cause` can be included
|
|
209
|
-
*/
|
|
210
|
-
protected constructor(message: string, code: string, metadata?: DomainErrorMetadata);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
type Primitive = boolean | number | string;
|
|
214
384
|
/**
|
|
215
385
|
* Base class for primitive-based Value Objects.
|
|
216
386
|
*
|
|
@@ -229,12 +399,13 @@ type Primitive = boolean | number | string;
|
|
|
229
399
|
* - Username
|
|
230
400
|
* - Slug
|
|
231
401
|
*/
|
|
232
|
-
declare abstract class PrimitiveValueObject<T extends Primitive> implements EntityId {
|
|
402
|
+
declare abstract class PrimitiveValueObject<T extends Primitive$2> implements EntityId {
|
|
403
|
+
#private;
|
|
233
404
|
/**
|
|
234
405
|
* The underlying primitive value.
|
|
235
406
|
* Guaranteed to be valid after construction.
|
|
236
407
|
*/
|
|
237
|
-
|
|
408
|
+
get value(): T;
|
|
238
409
|
/**
|
|
239
410
|
* Constructs a new PrimitiveValueObject.
|
|
240
411
|
*
|
|
@@ -251,290 +422,376 @@ declare abstract class PrimitiveValueObject<T extends Primitive> implements Enti
|
|
|
251
422
|
*
|
|
252
423
|
* @param other - Another Value Object
|
|
253
424
|
*/
|
|
254
|
-
equals(other
|
|
425
|
+
equals(other: any): boolean;
|
|
255
426
|
/**
|
|
256
427
|
* Returns the primitive value.
|
|
257
428
|
* Prefer explicit access over implicit coercion.
|
|
429
|
+
* @deprecated - instead use instance.value
|
|
258
430
|
*/
|
|
259
431
|
getValue(): T;
|
|
260
|
-
/**
|
|
261
|
-
* JSON serialization hook.
|
|
262
|
-
* Produces the raw primitive value.
|
|
263
|
-
*/
|
|
264
|
-
toJSON(): T;
|
|
265
432
|
/**
|
|
266
433
|
* String representation.
|
|
267
434
|
* Useful for logging and debugging.
|
|
268
|
-
*/
|
|
269
|
-
toString(): string;
|
|
270
|
-
/**
|
|
271
|
-
* Domain invariant validation.
|
|
272
|
-
* Must throw if the value is invalid.
|
|
273
|
-
*
|
|
274
|
-
* @param value - The value to validate
|
|
275
|
-
*/
|
|
276
|
-
protected abstract validate(value: T): void;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
declare abstract class ValueObject<T> {
|
|
280
|
-
get value(): T;
|
|
281
|
-
protected readonly props: Readonly<T>;
|
|
282
|
-
protected constructor(props: T);
|
|
283
|
-
/**
|
|
284
|
-
* Type guard to check if an unknown object is an instance of ValueObject.
|
|
285
|
-
* This is useful for runtime type checking.
|
|
286
|
-
*
|
|
287
|
-
* @param vo The object to check.
|
|
288
|
-
* @returns True if the object is a ValueObject instance, false otherwise.
|
|
289
|
-
*/
|
|
290
|
-
static is(vo: unknown): vo is ValueObject<unknown>;
|
|
291
|
-
/**
|
|
292
|
-
* Deep equality comparison of ValueObjects
|
|
293
|
-
*/
|
|
294
|
-
equals(other?: ValueObject<T>): boolean;
|
|
295
|
-
/**
|
|
296
|
-
* Standard for clean API integration and logging.
|
|
297
|
-
*/
|
|
298
|
-
toJSON(): T;
|
|
299
|
-
/**
|
|
300
|
-
* Useful for debugging and string-based indexing.
|
|
301
|
-
*/
|
|
302
|
-
toString(): string;
|
|
303
|
-
/**
|
|
304
|
-
* Validates the value object props
|
|
305
|
-
* @throws InvalidValueObjectError if validation fails
|
|
306
|
-
*/
|
|
307
|
-
protected abstract validate(props: T): void;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Custom error class for entity validation failures.
|
|
312
|
-
*/
|
|
313
|
-
declare class EntityValidationError extends DomainError$1 {
|
|
314
|
-
constructor(message: string, cause?: Error);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
declare class InvalidValueObjectError extends DomainError$1 {
|
|
318
|
-
constructor(message: string);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Represents a UUID (Universally Unique Identifier) value object.
|
|
323
|
-
*
|
|
324
|
-
* This class extends PrimitiveValueObject to provide type-safe UUID handling
|
|
325
|
-
* with validation using Zod schema. UUIDs are immutable and can be generated
|
|
326
|
-
* randomly or created from string values.
|
|
327
|
-
*
|
|
328
|
-
* @example
|
|
329
|
-
* // Generate a new random UUID
|
|
330
|
-
* const id = UUID.generate();
|
|
331
|
-
*
|
|
332
|
-
* @example
|
|
333
|
-
* // Create UUID from an existing string
|
|
334
|
-
* const id = UUID.fromString('550e8400-e29b-41d4-a716-446655440000');
|
|
335
|
-
*
|
|
336
|
-
* @throws {InvalidValueObjectError} When the provided string is not a valid UUID format
|
|
337
|
-
*/
|
|
338
|
-
declare class UUID extends PrimitiveValueObject<string> {
|
|
339
|
-
private static readonly schema;
|
|
340
|
-
constructor(value: string);
|
|
341
|
-
/**
|
|
342
|
-
* Creates an UUID from an external string.
|
|
343
|
-
* Use only for untrusted input.
|
|
344
|
-
*
|
|
345
|
-
* @param value - UUID string
|
|
346
|
-
*/
|
|
347
|
-
static fromString(value: string): UUID;
|
|
348
|
-
/**
|
|
349
|
-
* Generates a new AggregateId.
|
|
350
|
-
*/
|
|
351
|
-
static generate<T extends typeof UUID>(this: T): InstanceType<T>;
|
|
352
|
-
protected validate(value: string): void;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* AggregateId represents a strongly-typed aggregate identifier.
|
|
357
|
-
*
|
|
358
|
-
* - Backed by UUID v4
|
|
359
|
-
* - Immutable
|
|
360
|
-
* - Comparable only to AggregateId
|
|
361
|
-
*/
|
|
362
|
-
declare class AggregateId extends UUID {
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* HTTP status code catalog.
|
|
367
|
-
*
|
|
368
|
-
* This object provides a typed, immutable map of standard HTTP status names
|
|
369
|
-
* to their numeric codes. Designed for server-side frameworks such as Fastify.
|
|
370
|
-
*
|
|
371
|
-
* - All identifiers use clear, canonical semantic names.
|
|
372
|
-
* - Values are numeric status codes.
|
|
373
|
-
* - Frozen to prevent runtime mutation.
|
|
374
|
-
* - Exporting `HttpStatus` ensures type-safe usage across the codebase.
|
|
375
|
-
* Usage:
|
|
376
|
-
* ```ts
|
|
377
|
-
* import { HttpStatus } from 'path-to-this-file';
|
|
378
|
-
*
|
|
379
|
-
* function handleRequest() {
|
|
380
|
-
* return {
|
|
381
|
-
* statusCode: HttpStatus.OK,
|
|
382
|
-
* body: 'Success',
|
|
383
|
-
* };
|
|
384
|
-
* }
|
|
385
|
-
* ```
|
|
386
|
-
*/
|
|
387
|
-
declare const HttpStatus: Readonly<{
|
|
388
|
-
readonly REQUEST_HEADER_FIELDS_TOO_LARGE: 431;
|
|
389
|
-
readonly NETWORK_AUTHENTICATION_REQUIRED: 511;
|
|
390
|
-
readonly NON_AUTHORITATIVE_INFORMATION: 203;
|
|
391
|
-
readonly PROXY_AUTHENTICATION_REQUIRED: 407;
|
|
392
|
-
readonly UNAVAILABLE_FOR_LEGAL_REASONS: 451;
|
|
393
|
-
readonly HTTP_VERSION_NOT_SUPPORTED: 505;
|
|
394
|
-
readonly BANDWIDTH_LIMIT_EXCEEDED: 509;
|
|
395
|
-
readonly VARIANT_ALSO_NEGOTIATES: 506;
|
|
396
|
-
readonly UNSUPPORTED_MEDIA_TYPE: 415;
|
|
397
|
-
readonly RANGE_NOT_SATISFIABLE: 416;
|
|
398
|
-
readonly PRECONDITION_REQUIRED: 428;
|
|
399
|
-
readonly INTERNAL_SERVER_ERROR: 500;
|
|
400
|
-
readonly UNPROCESSABLE_ENTITY: 422;
|
|
401
|
-
readonly INSUFFICIENT_STORAGE: 507;
|
|
402
|
-
readonly SWITCHING_PROTOCOLS: 101;
|
|
403
|
-
readonly PRECONDITION_FAILED: 412;
|
|
404
|
-
readonly MISDIRECTED_REQUEST: 421;
|
|
405
|
-
readonly SERVICE_UNAVAILABLE: 503;
|
|
406
|
-
readonly TEMPORARY_REDIRECT: 307;
|
|
407
|
-
readonly PERMANENT_REDIRECT: 308;
|
|
408
|
-
readonly METHOD_NOT_ALLOWED: 405;
|
|
409
|
-
readonly EXPECTATION_FAILED: 417;
|
|
410
|
-
readonly MOVED_PERMANENTLY: 301;
|
|
411
|
-
readonly PAYLOAD_TOO_LARGE: 413;
|
|
412
|
-
readonly FAILED_DEPENDENCY: 424;
|
|
413
|
-
readonly TOO_MANY_REQUESTS: 429;
|
|
414
|
-
readonly ALREADY_REPORTED: 208;
|
|
415
|
-
readonly MULTIPLE_CHOICES: 300;
|
|
416
|
-
readonly PAYMENT_REQUIRED: 402;
|
|
417
|
-
readonly UPGRADE_REQUIRED: 426;
|
|
418
|
-
readonly PARTIAL_CONTENT: 206;
|
|
419
|
-
readonly REQUEST_TIMEOUT: 408;
|
|
420
|
-
readonly LENGTH_REQUIRED: 411;
|
|
421
|
-
readonly NOT_IMPLEMENTED: 501;
|
|
422
|
-
readonly GATEWAY_TIMEOUT: 504;
|
|
423
|
-
readonly NOT_ACCEPTABLE: 406;
|
|
424
|
-
readonly RESET_CONTENT: 205;
|
|
425
|
-
readonly LOOP_DETECTED: 508;
|
|
426
|
-
readonly MULTI_STATUS: 207;
|
|
427
|
-
readonly NOT_MODIFIED: 304;
|
|
428
|
-
readonly UNAUTHORIZED: 401;
|
|
429
|
-
readonly URI_TOO_LONG: 414;
|
|
430
|
-
readonly NOT_EXTENDED: 510;
|
|
431
|
-
readonly EARLY_HINTS: 103;
|
|
432
|
-
readonly BAD_REQUEST: 400;
|
|
433
|
-
readonly IM_A_TEAPOT: 418;
|
|
434
|
-
readonly BAD_GATEWAY: 502;
|
|
435
|
-
readonly PROCESSING: 102;
|
|
436
|
-
readonly NO_CONTENT: 204;
|
|
437
|
-
readonly SEE_OTHER: 303;
|
|
438
|
-
readonly USE_PROXY: 305;
|
|
439
|
-
readonly FORBIDDEN: 403;
|
|
440
|
-
readonly NOT_FOUND: 404;
|
|
441
|
-
readonly TOO_EARLY: 425;
|
|
442
|
-
readonly CONTINUE: 100;
|
|
443
|
-
readonly ACCEPTED: 202;
|
|
444
|
-
readonly CONFLICT: 409;
|
|
445
|
-
readonly CREATED: 201;
|
|
446
|
-
readonly IM_USED: 226;
|
|
447
|
-
readonly LOCKED: 423;
|
|
448
|
-
readonly FOUND: 302;
|
|
449
|
-
readonly GONE: 410;
|
|
450
|
-
readonly OK: 200;
|
|
451
|
-
}>;
|
|
452
|
-
type HttpStatusCode = keyof typeof HttpStatusMessage;
|
|
453
|
-
/**
|
|
454
|
-
* HTTP status messages mapped by numeric code.
|
|
455
|
-
* Use for sending descriptive text in responses or logging.
|
|
456
|
-
*/
|
|
457
|
-
declare const HttpStatusMessage: {
|
|
458
|
-
readonly 431: "Request Header Fields Too Large";
|
|
459
|
-
readonly 511: "Network Authentication Required";
|
|
460
|
-
readonly 203: "Non-Authoritative Information";
|
|
461
|
-
readonly 407: "Proxy Authentication Required";
|
|
462
|
-
readonly 451: "Unavailable For Legal Reasons";
|
|
463
|
-
readonly 505: "HTTP Version Not Supported";
|
|
464
|
-
readonly 509: "Bandwidth Limit Exceeded";
|
|
465
|
-
readonly 506: "Variant Also Negotiates";
|
|
466
|
-
readonly 415: "Unsupported Media Type";
|
|
467
|
-
readonly 416: "Range Not Satisfiable";
|
|
468
|
-
readonly 428: "Precondition Required";
|
|
469
|
-
readonly 500: "Internal Server Error";
|
|
470
|
-
readonly 422: "Unprocessable Entity";
|
|
471
|
-
readonly 507: "Insufficient Storage";
|
|
472
|
-
readonly 101: "Switching Protocols";
|
|
473
|
-
readonly 412: "Precondition Failed";
|
|
474
|
-
readonly 421: "Misdirected Request";
|
|
475
|
-
readonly 503: "Service Unavailable";
|
|
476
|
-
readonly 307: "Temporary Redirect";
|
|
477
|
-
readonly 308: "Permanent Redirect";
|
|
478
|
-
readonly 405: "Method Not Allowed";
|
|
479
|
-
readonly 417: "Expectation Failed";
|
|
480
|
-
readonly 301: "Moved Permanently";
|
|
481
|
-
readonly 413: "Payload Too Large";
|
|
482
|
-
readonly 424: "Failed Dependency";
|
|
483
|
-
readonly 429: "Too Many Requests";
|
|
484
|
-
readonly 208: "Already Reported";
|
|
485
|
-
readonly 300: "Multiple Choices";
|
|
486
|
-
readonly 402: "Payment Required";
|
|
487
|
-
readonly 426: "Upgrade Required";
|
|
488
|
-
readonly 206: "Partial Content";
|
|
489
|
-
readonly 408: "Request Timeout";
|
|
490
|
-
readonly 411: "Length Required";
|
|
491
|
-
readonly 501: "Not Implemented";
|
|
492
|
-
readonly 504: "Gateway Timeout";
|
|
493
|
-
readonly 406: "Not Acceptable";
|
|
494
|
-
readonly 205: "Reset Content";
|
|
495
|
-
readonly 508: "Loop Detected";
|
|
496
|
-
readonly 207: "Multi-Status";
|
|
497
|
-
readonly 304: "Not Modified";
|
|
498
|
-
readonly 401: "Unauthorized";
|
|
499
|
-
readonly 414: "URI Too Long";
|
|
500
|
-
readonly 418: "I'm a Teapot";
|
|
501
|
-
readonly 510: "Not Extended";
|
|
502
|
-
readonly 103: "Early Hints";
|
|
503
|
-
readonly 400: "Bad Request";
|
|
504
|
-
readonly 502: "Bad Gateway";
|
|
505
|
-
readonly 102: "Processing";
|
|
506
|
-
readonly 204: "No Content";
|
|
507
|
-
readonly 303: "See Other";
|
|
508
|
-
readonly 305: "Use Proxy";
|
|
509
|
-
readonly 403: "Forbidden";
|
|
510
|
-
readonly 404: "Not Found";
|
|
511
|
-
readonly 425: "Too Early";
|
|
512
|
-
readonly 100: "Continue";
|
|
513
|
-
readonly 202: "Accepted";
|
|
514
|
-
readonly 226: "I'm Used";
|
|
515
|
-
readonly 409: "Conflict";
|
|
516
|
-
readonly 201: "Created";
|
|
517
|
-
readonly 423: "Locked";
|
|
518
|
-
readonly 302: "Found";
|
|
519
|
-
readonly 410: "Gone";
|
|
520
|
-
readonly 200: "OK";
|
|
521
|
-
};
|
|
522
|
-
type HttpStatusMessage = (typeof HttpStatusMessage)[keyof typeof HttpStatusMessage];
|
|
435
|
+
*/
|
|
436
|
+
toString(): string;
|
|
437
|
+
/**
|
|
438
|
+
* Domain invariant validation.
|
|
439
|
+
* Must throw if the value is invalid.
|
|
440
|
+
*
|
|
441
|
+
* @param value - The value to validate
|
|
442
|
+
*/
|
|
443
|
+
protected abstract validate(value: T): void;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
declare abstract class ValueObject<T> {
|
|
447
|
+
get value(): T;
|
|
448
|
+
protected readonly props: Readonly<T>;
|
|
449
|
+
protected constructor(props: T);
|
|
450
|
+
/**
|
|
451
|
+
* Type guard to check if an unknown object is an instance of ValueObject.
|
|
452
|
+
* This is useful for runtime type checking.
|
|
453
|
+
*
|
|
454
|
+
* @param vo The object to check.
|
|
455
|
+
* @returns True if the object is a ValueObject instance, false otherwise.
|
|
456
|
+
*/
|
|
457
|
+
static is(vo: unknown): vo is ValueObject<unknown>;
|
|
458
|
+
/**
|
|
459
|
+
* Deep equality comparison of ValueObjects
|
|
460
|
+
*/
|
|
461
|
+
equals(other?: ValueObject<T>): boolean;
|
|
462
|
+
/**
|
|
463
|
+
* Standard for clean API integration and logging.
|
|
464
|
+
*/
|
|
465
|
+
toJSON(): T;
|
|
466
|
+
/**
|
|
467
|
+
* Useful for debugging and string-based indexing.
|
|
468
|
+
*/
|
|
469
|
+
toString(): string;
|
|
470
|
+
/**
|
|
471
|
+
* Validates the value object props
|
|
472
|
+
* @throws InvalidValueObjectError if validation fails
|
|
473
|
+
*/
|
|
474
|
+
protected abstract validate(props: T): void;
|
|
475
|
+
}
|
|
523
476
|
|
|
477
|
+
type Primitive = boolean | number | string | null | undefined;
|
|
478
|
+
type Metadata<T = {}> = T extends Record<string, Primitive> ? T : {};
|
|
479
|
+
/**
|
|
480
|
+
* Categories of domain errors based on the nature of the violation.
|
|
481
|
+
*
|
|
482
|
+
* @typedef {'DOMAIN.INVALID_STATE' | 'DOMAIN.INVALID_VALUE'} DomainErrorType
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* // DomainErrorType usage:
|
|
486
|
+
* const errorType: DomainErrorType = 'DOMAIN.INVALID_STATE';
|
|
487
|
+
*/
|
|
488
|
+
type DomainErrorType = 'DOMAIN.INVALID_STATE' | 'DOMAIN.INVALID_VALUE';
|
|
489
|
+
type ValueOf<T> = T[keyof T];
|
|
490
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
491
|
+
/**
|
|
492
|
+
* Interface for declaring domain error namespaces via declaration merging.
|
|
493
|
+
* Projects extend this interface to add their own namespaces and error codes.
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* // In your project's type definition file (.d.ts):
|
|
497
|
+
* declare module '@your-org/domain-errors' {
|
|
498
|
+
* interface DomainErrorNamespaces {
|
|
499
|
+
* USER: ['NOT_FOUND', 'INVALID_EMAIL', 'SUSPENDED'];
|
|
500
|
+
* ORDER: ['NOT_FOUND', 'INVALID_STATUS', 'OUT_OF_STOCK'];
|
|
501
|
+
* // Override default namespace (optional):
|
|
502
|
+
* CORE: ['INTERNAL_ERROR', 'VALIDATION_FAILED', 'CONFIGURATION_ERROR'];
|
|
503
|
+
* }
|
|
504
|
+
* }
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* // This enables type-safe error codes:
|
|
508
|
+
* const code: DomainErrorCode = 'USER.NOT_FOUND'; // ✅ Valid
|
|
509
|
+
* const code: DomainErrorCode = 'USER.INVALID'; // ❌ TypeScript error
|
|
510
|
+
*/
|
|
511
|
+
interface DomainErrorNamespaces {
|
|
512
|
+
DOMAIN: ['INVALID_VALUE', 'INVALID_STATE'];
|
|
513
|
+
CORE: [
|
|
514
|
+
'INTERNAL_ERROR',
|
|
515
|
+
'VALIDATION_FAILED',
|
|
516
|
+
'CONFIGURATION_ERROR',
|
|
517
|
+
'NOT_IMPLEMENTED'
|
|
518
|
+
];
|
|
519
|
+
SYSTEM: ['UNEXPECTED', 'TIMEOUT', 'NETWORK_ERROR', 'DEPENDENCY_ERROR'];
|
|
520
|
+
}
|
|
521
|
+
type Namespace = keyof DomainErrorNamespaces;
|
|
522
|
+
type ErrorName<N extends Namespace> = DomainErrorNamespaces[N][number];
|
|
523
|
+
/**
|
|
524
|
+
* Union type of all valid domain error codes derived from registered namespaces.
|
|
525
|
+
* Automatically updates when projects extend DomainErrorNamespaces.
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* // After extending DomainErrorNamespaces with USER namespace:
|
|
529
|
+
* type ErrorCode = DomainErrorCode;
|
|
530
|
+
* // Becomes: 'CORE.INTERNAL_ERROR' | 'CORE.VALIDATION_FAILED' |
|
|
531
|
+
* // 'USER.NOT_FOUND' | 'USER.INVALID_EMAIL' | ...
|
|
532
|
+
*/
|
|
533
|
+
type DomainErrorCode = {
|
|
534
|
+
[N in Namespace]: `${Uppercase<string & N>}.${Uppercase<ErrorName<N>>}`;
|
|
535
|
+
}[Namespace];
|
|
536
|
+
type ExtractNamespace<Code extends DomainErrorCode> = Code extends `${infer N}.${string}` ? N : never;
|
|
537
|
+
type ExtractErrorName<Code extends DomainErrorCode> = Code extends `${string}.${infer E}` ? E : never;
|
|
524
538
|
/**
|
|
525
|
-
*
|
|
526
|
-
*
|
|
539
|
+
* Base class for all domain errors in a Domain-Driven Design architecture.
|
|
540
|
+
*
|
|
541
|
+
* Domain errors represent violations of business rules and domain invariants.
|
|
542
|
+
* They are pure value objects without infrastructure concerns like IDs or timestamps.
|
|
543
|
+
*
|
|
544
|
+
* @typeParam Code - The specific error code from DomainErrorCode union
|
|
545
|
+
* @typeParam Meta - Type of metadata associated with this error
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* // 1. First, declare your namespaces:
|
|
549
|
+
* declare module '@your-org/domain-errors' {
|
|
550
|
+
* interface DomainErrorNamespaces {
|
|
551
|
+
* USER: ['NOT_FOUND', 'INVALID_EMAIL'];
|
|
552
|
+
* }
|
|
553
|
+
* }
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* // 2. Create a concrete domain error:
|
|
557
|
+
* class UserNotFoundError extends DomainError<'USER.NOT_FOUND', { userId: string }> {
|
|
558
|
+
* public readonly code = 'USER.NOT_FOUND' as const;
|
|
559
|
+
* public readonly type: DomainErrorType = 'DOMAIN.INVALID_VALUE';
|
|
560
|
+
*
|
|
561
|
+
* constructor(userId: string) {
|
|
562
|
+
* super(`User with ID '${userId}' not found`, { userId });
|
|
563
|
+
* }
|
|
564
|
+
* }
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* // 3. Usage in domain services:
|
|
568
|
+
* class UserService {
|
|
569
|
+
* async activateUser(userId: string): Promise<Result<User, DomainError>> {
|
|
570
|
+
* const user = await this.repository.findById(userId);
|
|
571
|
+
*
|
|
572
|
+
* if (!user) {
|
|
573
|
+
* return Result.failure(new UserNotFoundError(userId));
|
|
574
|
+
* }
|
|
575
|
+
*
|
|
576
|
+
* if (user.isSuspended) {
|
|
577
|
+
* return Result.failure(new UserSuspendedError(userId));
|
|
578
|
+
* }
|
|
579
|
+
*
|
|
580
|
+
* // Business logic...
|
|
581
|
+
* }
|
|
582
|
+
* }
|
|
583
|
+
*
|
|
584
|
+
* @abstract
|
|
527
585
|
*/
|
|
528
|
-
declare abstract class DomainError {
|
|
529
|
-
|
|
586
|
+
declare abstract class DomainError<Meta extends Record<string, Primitive> = {}, Code extends DomainErrorCode = DomainErrorCode> {
|
|
587
|
+
/**
|
|
588
|
+
* Machine-readable error code in format: NAMESPACE.ERROR_NAME
|
|
589
|
+
*
|
|
590
|
+
* @remarks
|
|
591
|
+
* - Must be uppercase (e.g., 'USER.NOT_FOUND')
|
|
592
|
+
* - Namespace must be declared in DomainErrorNamespaces
|
|
593
|
+
* - Error name must be in the namespace's array
|
|
594
|
+
*
|
|
595
|
+
* @example
|
|
596
|
+
* public readonly code = 'USER.NOT_FOUND' as const;
|
|
597
|
+
*/
|
|
598
|
+
abstract readonly code: Code;
|
|
599
|
+
/**
|
|
600
|
+
* Human-readable error message describing the domain rule violation.
|
|
601
|
+
* Should be meaningful to developers and potentially end-users.
|
|
602
|
+
*
|
|
603
|
+
* @remarks
|
|
604
|
+
* - Avoid technical implementation details
|
|
605
|
+
* - Focus on the business rule that was violated
|
|
606
|
+
* - Can include values from metadata for context
|
|
607
|
+
*
|
|
608
|
+
* @example
|
|
609
|
+
* // Good: "Order amount $150 exceeds maximum limit of $100"
|
|
610
|
+
* // Bad: "Amount validation failed: 150 > 100"
|
|
611
|
+
*/
|
|
530
612
|
readonly message: string;
|
|
531
|
-
|
|
532
|
-
|
|
613
|
+
/**
|
|
614
|
+
* Immutable structured context providing additional information about the error.
|
|
615
|
+
* Useful for debugging, logging, and creating detailed error messages.
|
|
616
|
+
*
|
|
617
|
+
* @remarks
|
|
618
|
+
* - Values must be primitive types (string, number, boolean, etc.)
|
|
619
|
+
* - Object is frozen to prevent mutation
|
|
620
|
+
* - Type-safe based on the error code
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* // For UserNotFoundError:
|
|
624
|
+
* { userId: 'usr_123', attemptedAction: 'activate' }
|
|
625
|
+
*
|
|
626
|
+
* @readonly
|
|
627
|
+
*/
|
|
628
|
+
readonly metadata: Readonly<Meta>;
|
|
629
|
+
/**
|
|
630
|
+
* Category of domain error indicating the nature of violation.
|
|
631
|
+
*
|
|
632
|
+
* @remarks
|
|
633
|
+
* Use 'DOMAIN.INVALID_STATE' for state violations (e.g., "Cannot checkout empty cart")
|
|
634
|
+
* Use 'DOMAIN.INVALID_VALUE' for value violations (e.g., "Email format is invalid")
|
|
635
|
+
*
|
|
636
|
+
* @example
|
|
637
|
+
* public readonly type: DomainErrorType = 'DOMAIN.INVALID_VALUE';
|
|
638
|
+
*/
|
|
639
|
+
abstract readonly type: DomainErrorType;
|
|
640
|
+
/** Get error name from error code */
|
|
641
|
+
get errorName(): ExtractErrorName<Code>;
|
|
642
|
+
/** Get namespace from error code */
|
|
643
|
+
get namespace(): ExtractNamespace<Code>;
|
|
644
|
+
/**
|
|
645
|
+
* Creates a new DomainError instance.
|
|
646
|
+
*
|
|
647
|
+
* @param message - Human-readable description of the domain rule violation
|
|
648
|
+
* @param metadata - Optional structured context (primitive values only)
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* constructor(userId: string) {
|
|
652
|
+
* super(`User with ID '${userId}' not found`, { userId });
|
|
653
|
+
* }
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* constructor(amount: number, maxLimit: number) {
|
|
657
|
+
* super(
|
|
658
|
+
* `Order amount $${amount} exceeds maximum limit of $${maxLimit}`,
|
|
659
|
+
* { amount, maxLimit }
|
|
660
|
+
* );
|
|
661
|
+
* }
|
|
662
|
+
*/
|
|
663
|
+
protected constructor(message: string, ...args: keyof Meta extends never ? [] | [metadata?: Meta] : [metadata: Meta]);
|
|
664
|
+
/**
|
|
665
|
+
* Serializes the error to a plain object for debugging, logging, or transport.
|
|
666
|
+
* Does not include infrastructure concerns like stack traces or timestamps.
|
|
667
|
+
*
|
|
668
|
+
* @returns Plain object with error details
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* const error = new UserNotFoundError('usr_123');
|
|
672
|
+
* const json = error.toJSON();
|
|
673
|
+
* // Result:
|
|
674
|
+
* // {
|
|
675
|
+
* // code: 'USER.NOT_FOUND',
|
|
676
|
+
* // message: "User with ID 'usr_123' not found",
|
|
677
|
+
* // type: 'DOMAIN.INVALID_VALUE',
|
|
678
|
+
* // metadata: { userId: 'usr_123' }
|
|
679
|
+
* // }
|
|
680
|
+
*/
|
|
681
|
+
toObject(): {
|
|
682
|
+
metadata: Readonly<Meta>;
|
|
533
683
|
message: string;
|
|
534
|
-
|
|
535
|
-
|
|
684
|
+
code: Code;
|
|
685
|
+
type: DomainErrorType;
|
|
686
|
+
};
|
|
687
|
+
/**
|
|
688
|
+
* Returns a string representation of the error.
|
|
689
|
+
* Format: [CODE] MESSAGE
|
|
690
|
+
*
|
|
691
|
+
* @returns Human-readable string representation
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* const error = new UserNotFoundError('usr_123');
|
|
695
|
+
* console.log(error.toString());
|
|
696
|
+
* // Output: [USER.NOT_FOUND] User with ID 'usr_123' not found
|
|
697
|
+
*
|
|
698
|
+
* @override
|
|
699
|
+
*/
|
|
700
|
+
toString(): string;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Default domain errors for common scenarios.
|
|
705
|
+
* These errors are available across all projects using this module.
|
|
706
|
+
*/
|
|
707
|
+
/**
|
|
708
|
+
* Error thrown when an unexpected internal error occurs.
|
|
709
|
+
* Typically used for programming bugs, invalid assumptions, or states that should never happen.
|
|
710
|
+
*
|
|
711
|
+
* @remarks
|
|
712
|
+
* - Metadata is optional for empty metadata types and required if a non-empty type is provided.
|
|
713
|
+
* - Useful for debugging, logging, and adding context to unexpected failures.
|
|
714
|
+
*
|
|
715
|
+
* @example
|
|
716
|
+
* // Catch a programming error:
|
|
717
|
+
* try {
|
|
718
|
+
* complexBusinessLogic();
|
|
719
|
+
* } catch (error) {
|
|
720
|
+
* throw new InternalError({
|
|
721
|
+
* message: 'Unexpected error in complexBusinessLogic',
|
|
722
|
+
* metadata: { originalError: error.message, timestamp: Date.now() }
|
|
723
|
+
* });
|
|
724
|
+
* }
|
|
725
|
+
*
|
|
726
|
+
* @example
|
|
727
|
+
* // Fallback for unhandled cases:
|
|
728
|
+
* switch (status) {
|
|
729
|
+
* case 'PENDING':
|
|
730
|
+
* break;
|
|
731
|
+
* case 'COMPLETED':
|
|
732
|
+
* break;
|
|
733
|
+
* default:
|
|
734
|
+
* throw new InternalError({
|
|
735
|
+
* message: `Unhandled status: ${status}`,
|
|
736
|
+
* metadata: { status }
|
|
737
|
+
* });
|
|
738
|
+
* }
|
|
739
|
+
*/
|
|
740
|
+
declare class InternalError<T = Record<string, Primitive>> extends DomainError<Metadata<T>> {
|
|
741
|
+
/** @inheritdoc */
|
|
742
|
+
readonly code: "CORE.INTERNAL_ERROR";
|
|
743
|
+
/** @inheritdoc */
|
|
744
|
+
readonly type: DomainErrorType;
|
|
745
|
+
/**
|
|
746
|
+
* Creates a new InternalError.
|
|
747
|
+
*
|
|
748
|
+
* @param message - Description of the internal error
|
|
749
|
+
* @param metadata - Optional debug information
|
|
750
|
+
*/
|
|
751
|
+
constructor(message?: string, metadata?: Metadata<T>);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Error thrown when an operation times out.
|
|
756
|
+
* Use for operations that exceed their allowed execution time.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* // Operation timeout:
|
|
760
|
+
* const timeout = setTimeout(() => {
|
|
761
|
+
* throw new TimeoutError(
|
|
762
|
+
* 'User registration timed out'
|
|
763
|
+
* );
|
|
764
|
+
* }, 5000);
|
|
765
|
+
*
|
|
766
|
+
* @example
|
|
767
|
+
* // With Promise.race:
|
|
768
|
+
* type Props = { url: string, timeoutMs: number }
|
|
769
|
+
* async function fetchWithTimeout(url: string, timeoutMs: number) {
|
|
770
|
+
* const timeoutPromise = new Promise<never>((_, reject) => {
|
|
771
|
+
* setTimeout(() => {
|
|
772
|
+
* reject(new TimeoutError<Props>(
|
|
773
|
+
* `Request to ${url} timed out`,
|
|
774
|
+
* { url, timeoutMs }
|
|
775
|
+
* ));
|
|
776
|
+
* }, timeoutMs);
|
|
777
|
+
* });
|
|
778
|
+
*
|
|
779
|
+
* return await Promise.race([fetch(url), timeoutPromise]);
|
|
780
|
+
* }
|
|
781
|
+
*/
|
|
782
|
+
declare class TimeoutError<T = Record<string, Primitive>> extends DomainError<Metadata<T>> {
|
|
783
|
+
/** @inheritdoc */
|
|
784
|
+
readonly code: "SYSTEM.TIMEOUT";
|
|
785
|
+
/** @inheritdoc */
|
|
786
|
+
readonly type: DomainErrorType;
|
|
787
|
+
/**
|
|
788
|
+
* Creates a new TimeoutError.
|
|
789
|
+
*
|
|
790
|
+
* @param message - Description of the timeout
|
|
791
|
+
* @param metadata - Optional timeout context
|
|
792
|
+
*/
|
|
793
|
+
constructor(message: string, metadata?: Metadata<T>);
|
|
536
794
|
}
|
|
537
|
-
type DomainErrorCode = 'DOMAIN.INVALID_STATE' | 'DOMAIN.INVALID_VALUE';
|
|
538
795
|
|
|
539
796
|
/**
|
|
540
797
|
* Represents the result of an operation, which can be either a success or a failure.
|
|
@@ -878,64 +1135,138 @@ declare class Result<T, E> {
|
|
|
878
1135
|
}
|
|
879
1136
|
|
|
880
1137
|
/**
|
|
881
|
-
*
|
|
1138
|
+
* Custom error class for entity validation failures.
|
|
1139
|
+
*/
|
|
1140
|
+
declare class EntityValidationError extends DomainError {
|
|
1141
|
+
code: DomainErrorCode;
|
|
1142
|
+
type: DomainErrorType;
|
|
1143
|
+
static create(msg: string): EntityValidationError;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
type Params = {
|
|
1147
|
+
value: string;
|
|
1148
|
+
};
|
|
1149
|
+
type Props = Metadata<Params>;
|
|
1150
|
+
declare class InvalidValueObjectError extends DomainError<Props> {
|
|
1151
|
+
code: DomainErrorCode;
|
|
1152
|
+
type: DomainErrorType;
|
|
1153
|
+
static create(msg?: string, meta?: Props): InvalidValueObjectError;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Represents a UUID (Universally Unique Identifier) value object.
|
|
1158
|
+
*
|
|
1159
|
+
* This class extends PrimitiveValueObject to provide type-safe UUID handling
|
|
1160
|
+
* with validation using Zod schema. UUIDs are immutable and can be generated
|
|
1161
|
+
* randomly or created from string values.
|
|
1162
|
+
* @deprecated
|
|
1163
|
+
* @example
|
|
1164
|
+
* // Generate a new random UUID
|
|
1165
|
+
* const id = UUID.generate();
|
|
1166
|
+
*
|
|
1167
|
+
* @example
|
|
1168
|
+
* // Create UUID from an existing string
|
|
1169
|
+
* const id = UUID.fromString('550e8400-e29b-41d4-a716-446655440000');
|
|
882
1170
|
*
|
|
883
|
-
* @
|
|
884
|
-
* @param seen - A WeakSet to track already processed objects (for circular references).
|
|
885
|
-
* @returns A deeply frozen version of the input object.
|
|
1171
|
+
* @throws {InvalidValueObjectError} When the provided string is not a valid UUID format
|
|
886
1172
|
*/
|
|
887
|
-
declare
|
|
1173
|
+
declare class UUID extends PrimitiveValueObject<string> {
|
|
1174
|
+
private static readonly schema;
|
|
1175
|
+
constructor(value: string);
|
|
1176
|
+
/**
|
|
1177
|
+
* Creates an UUID from an external string.
|
|
1178
|
+
* Use only for untrusted input.
|
|
1179
|
+
*
|
|
1180
|
+
* @param value - UUID string
|
|
1181
|
+
*/
|
|
1182
|
+
static fromString(value: string): UUID;
|
|
1183
|
+
/**
|
|
1184
|
+
* Generates a new AggregateId.
|
|
1185
|
+
*/
|
|
1186
|
+
static generate<T extends typeof UUID>(this: T): InstanceType<T>;
|
|
1187
|
+
protected validate(value: string): void;
|
|
1188
|
+
}
|
|
888
1189
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1190
|
+
/**
|
|
1191
|
+
* AggregateId represents a strongly-typed aggregate identifier.
|
|
1192
|
+
*
|
|
1193
|
+
* - Backed by UUID v4
|
|
1194
|
+
* - Immutable
|
|
1195
|
+
* - Comparable only to AggregateId
|
|
1196
|
+
*/
|
|
1197
|
+
declare class AggregateId extends UUID {
|
|
896
1198
|
}
|
|
1199
|
+
|
|
897
1200
|
/**
|
|
898
|
-
*
|
|
1201
|
+
* UUID is a branded string type.
|
|
899
1202
|
*
|
|
900
|
-
*
|
|
901
|
-
*
|
|
1203
|
+
* - Prevents accidental use of arbitrary strings
|
|
1204
|
+
* - Requires explicit validation or construction
|
|
1205
|
+
* - Zero runtime cost after creation
|
|
1206
|
+
*/
|
|
1207
|
+
type UuID = Tagged<string, 'UUID'>;
|
|
1208
|
+
/**
|
|
1209
|
+
* Abstract base class for all domain identifiers.
|
|
902
1210
|
*
|
|
903
|
-
*
|
|
904
|
-
*
|
|
1211
|
+
* Responsibilities:
|
|
1212
|
+
* - Enforces that every DomainID wraps a primitive string value.
|
|
1213
|
+
* - Provides a type-safe static factory `fromString` for creating concrete IDs.
|
|
1214
|
+
* - Leaves domain-specific validation to subclasses via `validate`.
|
|
905
1215
|
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
*
|
|
913
|
-
*
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
1216
|
+
* Design Notes:
|
|
1217
|
+
* - The class cannot know how to validate the ID itself, because validation
|
|
1218
|
+
* rules differ between ID types (e.g., UUID v4, ULID, NanoID).
|
|
1219
|
+
* - Static factory uses `new this(value)` pattern; hence, base class is **not abstract** in TypeScript terms.
|
|
1220
|
+
* - Subclasses must implement `validate(value: string)` inherited from PrimitiveValueObject.
|
|
1221
|
+
*
|
|
1222
|
+
* Usage:
|
|
1223
|
+
* ```ts
|
|
1224
|
+
* class AuthAttemptId extends DomainID {
|
|
1225
|
+
* protected validate(value: string): void {
|
|
1226
|
+
* if (!isValidUuid(value)) {
|
|
1227
|
+
* throw new Error('Invalid AuthAttemptId');
|
|
1228
|
+
* }
|
|
917
1229
|
* }
|
|
918
1230
|
* }
|
|
1231
|
+
*
|
|
1232
|
+
* const id = AuthAttemptId.fromString('550e8400-e29b-41d4-a716-446655440000');
|
|
919
1233
|
* ```
|
|
920
1234
|
*/
|
|
921
|
-
declare abstract class
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1235
|
+
declare abstract class DomainID extends PrimitiveValueObject<UuID> {
|
|
1236
|
+
static schema: z.ZodUUID;
|
|
1237
|
+
constructor(value: string);
|
|
1238
|
+
/**
|
|
1239
|
+
* Creates a concrete DomainID from a trusted string value.
|
|
1240
|
+
*
|
|
1241
|
+
* - Validation is enforced in the subclass constructor.
|
|
1242
|
+
* - Intended for application or infrastructure layers to produce IDs.
|
|
1243
|
+
*
|
|
1244
|
+
* @template T - Concrete subclass of DomainID
|
|
1245
|
+
* @param this - The constructor of the concrete subclass
|
|
1246
|
+
* @param value - Raw string value of the ID
|
|
1247
|
+
* @returns Instance of the concrete DomainID subclass
|
|
1248
|
+
*/
|
|
1249
|
+
static fromString<T extends new (value: string) => DomainID>(this: T, value: string): InstanceType<T>;
|
|
1250
|
+
static generate<T extends new (value: string) => DomainID>(this: T): InstanceType<T>;
|
|
1251
|
+
protected validate(value: UuID): void;
|
|
933
1252
|
}
|
|
934
1253
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1254
|
+
declare class Email extends PrimitiveValueObject<string> {
|
|
1255
|
+
private static readonly schema;
|
|
1256
|
+
constructor(value: string);
|
|
1257
|
+
static fromString(value: string): Email;
|
|
1258
|
+
protected validate(value: string): void;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Deeply freezes an object graph to enforce runtime immutability.
|
|
1263
|
+
* - Handles arrays
|
|
1264
|
+
* - Handles circular references
|
|
1265
|
+
* - Skips functions
|
|
1266
|
+
* - Skips already frozen objects
|
|
1267
|
+
*
|
|
1268
|
+
* Intended for aggregate and value object state only.
|
|
1269
|
+
*/
|
|
1270
|
+
declare function deepFreeze<T>(value: T, seen?: WeakSet<object>): Readonly<T>;
|
|
940
1271
|
|
|
941
|
-
export { AggregateId, AggregateRoot, ApplicationError, type ApplicationServicePort,
|
|
1272
|
+
export { AggregateId, AggregateRoot, ApplicationError, type ApplicationServicePort, type CreateEventProps, DomainError, type DomainErrorCode, type DomainErrorNamespaces, type DomainErrorType, DomainEvent, type DomainEventPayload, DomainID, Email, Entity, type EntityId, type EntityProps, EntityValidationError, type ExtractErrorName, type ExtractNamespace, HttpStatus, type HttpStatusCode, HttpStatusMessage, type Immutable, InternalError, InvalidValueObjectError, type Metadata, type Primitive, PrimitiveValueObject, Result, TimeoutError, UUID, type UnionToIntersection, type UnixTimestampMillis, type UuID, ValueObject, type ValueOf, deepFreeze };
|