@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 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<T = string> {
37
- equals: (other?: EntityId<T> | null | undefined) => boolean;
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
- readonly props: Props;
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 props - Initial identity and metadata.
285
+ * @param params - Initial identity and metadata.
68
286
  */
69
- protected constructor(props: EntityProps<ID, Props>);
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, Payload> = {
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<AggregateId, T>);
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
- * @template EntityProps The type of the entity's properties.
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
- protected readonly value: T;
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?: PrimitiveValueObject<T> | null | undefined): boolean;
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
- * Strongly-typed domain error used in Result failures.
526
- * Does NOT extend native Error on purpose — infrastructure maps to transport later.
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
- abstract readonly code: DomainErrorCode;
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
- readonly metadata: Readonly<Record<string, boolean | number | string>>;
532
- protected constructor(params: {
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
- metadata?: Record<string, boolean | number | string>;
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
- * Utility to deeply freeze objects to ensure immutability - handles nested objects and arrays.
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
- * @param obj - The object to be deeply frozen.
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 function deepFreeze<T>(obj: T, seen?: WeakSet<object>): Readonly<T>;
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
- interface ErrorParams {
890
- message: string;
891
- code: HttpStatusMessage;
892
- status?: HttpStatusCode;
893
- metadata?: Record<string, unknown> | undefined;
894
- isOperational?: boolean;
895
- cause?: Error | undefined;
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
- * Abstract base class for application-level errors with structured error handling.
1201
+ * UUID is a branded string type.
899
1202
  *
900
- * Extends the native Error class to provide machine-readable error codes, HTTP status codes,
901
- * operational error classification, and optional metadata for better error tracking and client communication.
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
- * @abstract
904
- * @extends {Error}
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
- * @example
907
- * ```typescript
908
- * class UserNotFoundError extends ApplicationError {
909
- * constructor(userId: string) {
910
- * super({
911
- * code: HttpStatusMessage['404'],
912
- * status: 404,
913
- * isOperational: true,
914
- * message: `User with id ${userId} not found`,
915
- * metadata: { userId }
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 ApplicationError extends Error {
922
- /** Optional cause (linked error) */
923
- readonly cause?: Error | undefined;
924
- /** Machine-readable error code (e.g. `OTP_LOCKED`, `USER_NOT_FOUND`) */
925
- readonly code: HttpStatusMessage;
926
- /** Operational vs programmer error flag */
927
- readonly isOperational: boolean;
928
- /** Optional structured metadata for debugging or clients */
929
- readonly metadata?: Record<string, unknown> | undefined;
930
- /** HTTP status code intended for response layer */
931
- readonly status: HttpStatusCode;
932
- constructor({ code, isOperational, status, metadata, message, cause, }: ErrorParams);
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
- type UnwrapValueObject<T> = T extends ValueObject<infer V> ? UnwrapValueObject<V> : T extends (infer U)[] ? UnwrapValueObject<U>[] : T extends Map<infer K, infer V> ? Map<K, UnwrapValueObject<V>> : T extends Set<infer V> ? Set<UnwrapValueObject<V>> : T extends Date ? string : T extends object ? {
936
- [K in keyof T]: UnwrapValueObject<T[K]>;
937
- } : T;
938
- declare function unwrapValueObject<T>(input: T, seen?: WeakSet<object>): UnwrapValueObject<T>;
939
- declare function ensureObject<T>(input: UnwrapValueObject<T>): object;
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, DomainError$1 as DomainError, type DomainErrorMetadata, DomainEvent, type DomainEventPayload, Entity, type EntityId, type EntityProps, EntityValidationError, HttpStatus, type HttpStatusCode, HttpStatusMessage, InvalidValueObjectError, PrimitiveValueObject, type Props, Result, UUID, type UnixTimestampMillis, type UnwrapValueObject, ValueObject, deepFreeze, ensureObject, unwrapValueObject };
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 };