@rineex/ddd 2.1.0 → 3.0.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 +749 -325
- package/dist/index.d.ts +749 -325
- 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,48 @@
|
|
|
1
|
+
import { Primitive as Primitive$2, EmptyObject, Tagged } from 'type-fest';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
|
|
4
|
+
interface ErrorParams {
|
|
5
|
+
message: string;
|
|
6
|
+
code: string;
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
isOperational?: boolean;
|
|
9
|
+
cause?: Error;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Abstract base class for application-level errors with structured error handling.
|
|
13
|
+
*
|
|
14
|
+
* Extends the native Error class to provide machine-readable error codes, HTTP status codes,
|
|
15
|
+
* operational error classification, and optional metadata for better error tracking and client communication.
|
|
16
|
+
*
|
|
17
|
+
* @abstract
|
|
18
|
+
* @extends {Error}
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* class UserNotFoundError extends ApplicationError {
|
|
23
|
+
* constructor(userId: string) {
|
|
24
|
+
* super({
|
|
25
|
+
* code: HttpStatusMessage['404'],
|
|
26
|
+
* isOperational: true,
|
|
27
|
+
* message: `User with id ${userId} not found`,
|
|
28
|
+
* metadata: { userId }
|
|
29
|
+
* });
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare abstract class ApplicationError extends Error {
|
|
35
|
+
/** Optional cause (linked error) */
|
|
36
|
+
readonly cause?: Error | undefined;
|
|
37
|
+
/** Machine-readable error code (e.g. `OTP_LOCKED`, `USER_NOT_FOUND`) */
|
|
38
|
+
readonly code: string;
|
|
39
|
+
/** Operational vs programmer error flag */
|
|
40
|
+
readonly isOperational: boolean;
|
|
41
|
+
/** Optional structured metadata for debugging or clients */
|
|
42
|
+
readonly metadata?: Record<string, unknown> | undefined;
|
|
43
|
+
constructor({ isOperational, metadata, message, cause, code, }: ErrorParams);
|
|
44
|
+
}
|
|
45
|
+
|
|
1
46
|
/**
|
|
2
47
|
* Port interface for application services that execute commands or queries.
|
|
3
48
|
*
|
|
@@ -33,11 +78,15 @@ interface ApplicationServicePort<I, O> {
|
|
|
33
78
|
* (e.g., UUID, ULID, or Database Sequence).
|
|
34
79
|
* @template T - The underlying primitive type of the ID (usually string or number).
|
|
35
80
|
*/
|
|
36
|
-
interface EntityId
|
|
37
|
-
equals: (other?:
|
|
81
|
+
interface EntityId {
|
|
82
|
+
equals: <T extends EntityId>(other?: T) => boolean;
|
|
38
83
|
toString: () => string;
|
|
84
|
+
readonly value: Readonly<Primitive$2>;
|
|
39
85
|
}
|
|
40
86
|
|
|
87
|
+
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 ? {
|
|
88
|
+
readonly [K in keyof T]: Immutable<T[K]>;
|
|
89
|
+
} : T;
|
|
41
90
|
/**
|
|
42
91
|
* Configuration for the base Entity constructor.
|
|
43
92
|
* Forces a single-object argument pattern to avoid positional argument errors.
|
|
@@ -48,7 +97,7 @@ interface EntityProps<ID extends EntityId, Props> {
|
|
|
48
97
|
readonly id: ID;
|
|
49
98
|
/** Optional creation timestamp; defaults to 'now' if not provided */
|
|
50
99
|
readonly createdAt?: Date;
|
|
51
|
-
|
|
100
|
+
props: Props;
|
|
52
101
|
}
|
|
53
102
|
/**
|
|
54
103
|
* Abstract Base Entity for Domain-Driven Design (DDD).
|
|
@@ -58,15 +107,21 @@ interface EntityProps<ID extends EntityId, Props> {
|
|
|
58
107
|
* @template ID - The specific Identity Value Object type.
|
|
59
108
|
*/
|
|
60
109
|
declare abstract class Entity<ID extends EntityId, Props> {
|
|
110
|
+
#private;
|
|
61
111
|
/** The timestamp when this entity was first instantiated/created */
|
|
62
112
|
readonly createdAt: Date;
|
|
63
113
|
/** The immutable unique identifier for this entity */
|
|
64
114
|
readonly id: ID;
|
|
115
|
+
/**
|
|
116
|
+
* Read-only view of entity state.
|
|
117
|
+
* External code can never mutate internal state.
|
|
118
|
+
*/
|
|
119
|
+
protected get props(): Immutable<Props>;
|
|
65
120
|
/**
|
|
66
121
|
* Protected constructor to be called by subclasses.
|
|
67
|
-
* @param
|
|
122
|
+
* @param params - Initial identity and metadata.
|
|
68
123
|
*/
|
|
69
|
-
protected constructor(
|
|
124
|
+
protected constructor(params: EntityProps<ID, Props>);
|
|
70
125
|
/**
|
|
71
126
|
* Compares entities by identity.
|
|
72
127
|
* In DDD, two entities are considered equal if their IDs match,
|
|
@@ -87,6 +142,7 @@ declare abstract class Entity<ID extends EntityId, Props> {
|
|
|
87
142
|
* @throws {Error} Should throw a specific DomainError if validation fails.
|
|
88
143
|
*/
|
|
89
144
|
abstract validate(): void;
|
|
145
|
+
protected mutate(updater: (current: Props) => Props): void;
|
|
90
146
|
}
|
|
91
147
|
|
|
92
148
|
type Primitive$1 = boolean | number | string | null;
|
|
@@ -95,13 +151,14 @@ type Serializable = Primitive$1 | Serializable[] | {
|
|
|
95
151
|
};
|
|
96
152
|
type DomainEventPayload = Record<string, Serializable>;
|
|
97
153
|
type UnixTimestampMillis = number;
|
|
98
|
-
type DomainEventProps<AggregateId extends EntityId
|
|
154
|
+
type DomainEventProps<Payload, AggregateId extends EntityId> = {
|
|
99
155
|
id?: string;
|
|
100
156
|
aggregateId: AggregateId;
|
|
101
157
|
schemaVersion: number;
|
|
102
158
|
occurredAt: UnixTimestampMillis;
|
|
103
159
|
payload: Payload;
|
|
104
160
|
};
|
|
161
|
+
type CreateEventProps<EventProps, ID extends EntityId> = DomainEventProps<EventProps, ID>;
|
|
105
162
|
declare abstract class DomainEvent<AggregateId extends EntityId = EntityId, T extends DomainEventPayload = DomainEventPayload> {
|
|
106
163
|
readonly aggregateId: AggregateId;
|
|
107
164
|
abstract readonly eventName: string;
|
|
@@ -109,7 +166,7 @@ declare abstract class DomainEvent<AggregateId extends EntityId = EntityId, T ex
|
|
|
109
166
|
readonly occurredAt: number;
|
|
110
167
|
readonly payload: Readonly<T>;
|
|
111
168
|
readonly schemaVersion: number;
|
|
112
|
-
protected constructor(props: DomainEventProps<
|
|
169
|
+
protected constructor(props: DomainEventProps<T, AggregateId>);
|
|
113
170
|
toPrimitives(): Readonly<{
|
|
114
171
|
id: string;
|
|
115
172
|
eventName: string;
|
|
@@ -120,36 +177,28 @@ declare abstract class DomainEvent<AggregateId extends EntityId = EntityId, T ex
|
|
|
120
177
|
}>;
|
|
121
178
|
}
|
|
122
179
|
|
|
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
180
|
/**
|
|
151
181
|
* Base class for aggregate roots in DDD, encapsulating domain events and validation.
|
|
152
|
-
*
|
|
182
|
+
*
|
|
183
|
+
* Aggregate roots are entities that serve as entry points to aggregates. They:
|
|
184
|
+
* - Enforce invariants across the aggregate
|
|
185
|
+
* - Manage domain events
|
|
186
|
+
* - Define transaction boundaries
|
|
187
|
+
*
|
|
188
|
+
* @template ID - The type of the aggregate's identity (must extend EntityId)
|
|
189
|
+
* @template P - The type of the aggregate's properties
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* interface UserProps {
|
|
194
|
+
* email: string;
|
|
195
|
+
* isActive: boolean;
|
|
196
|
+
* }
|
|
197
|
+
*
|
|
198
|
+
* class User extends AggregateRoot<AggregateId, UserProps> {
|
|
199
|
+
* // Implementation...
|
|
200
|
+
* }
|
|
201
|
+
* ```
|
|
153
202
|
*/
|
|
154
203
|
declare abstract class AggregateRoot<ID extends EntityId, P> extends Entity<ID, P> {
|
|
155
204
|
/**
|
|
@@ -169,48 +218,6 @@ declare abstract class AggregateRoot<ID extends EntityId, P> extends Entity<ID,
|
|
|
169
218
|
pullDomainEvents(): readonly DomainEvent[];
|
|
170
219
|
}
|
|
171
220
|
|
|
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
221
|
/**
|
|
215
222
|
* Base class for primitive-based Value Objects.
|
|
216
223
|
*
|
|
@@ -229,12 +236,13 @@ type Primitive = boolean | number | string;
|
|
|
229
236
|
* - Username
|
|
230
237
|
* - Slug
|
|
231
238
|
*/
|
|
232
|
-
declare abstract class PrimitiveValueObject<T extends Primitive> implements EntityId {
|
|
239
|
+
declare abstract class PrimitiveValueObject<T extends Primitive$2> implements EntityId {
|
|
240
|
+
#private;
|
|
233
241
|
/**
|
|
234
242
|
* The underlying primitive value.
|
|
235
243
|
* Guaranteed to be valid after construction.
|
|
236
244
|
*/
|
|
237
|
-
|
|
245
|
+
get value(): T;
|
|
238
246
|
/**
|
|
239
247
|
* Constructs a new PrimitiveValueObject.
|
|
240
248
|
*
|
|
@@ -251,17 +259,13 @@ declare abstract class PrimitiveValueObject<T extends Primitive> implements Enti
|
|
|
251
259
|
*
|
|
252
260
|
* @param other - Another Value Object
|
|
253
261
|
*/
|
|
254
|
-
equals(other
|
|
262
|
+
equals(other: any): boolean;
|
|
255
263
|
/**
|
|
256
264
|
* Returns the primitive value.
|
|
257
265
|
* Prefer explicit access over implicit coercion.
|
|
266
|
+
* @deprecated - instead use instance.value
|
|
258
267
|
*/
|
|
259
268
|
getValue(): T;
|
|
260
|
-
/**
|
|
261
|
-
* JSON serialization hook.
|
|
262
|
-
* Produces the raw primitive value.
|
|
263
|
-
*/
|
|
264
|
-
toJSON(): T;
|
|
265
269
|
/**
|
|
266
270
|
* String representation.
|
|
267
271
|
* Useful for logging and debugging.
|
|
@@ -307,234 +311,411 @@ declare abstract class ValueObject<T> {
|
|
|
307
311
|
protected abstract validate(props: T): void;
|
|
308
312
|
}
|
|
309
313
|
|
|
314
|
+
type Primitive = boolean | number | string | null | undefined;
|
|
315
|
+
type Metadata<T = EmptyObject> = T extends Record<string, Primitive> ? T : EmptyObject;
|
|
310
316
|
/**
|
|
311
|
-
*
|
|
317
|
+
* Categories of domain errors based on the nature of the violation.
|
|
318
|
+
*
|
|
319
|
+
* @typedef {'DOMAIN.INVALID_STATE' | 'DOMAIN.INVALID_VALUE'} DomainErrorType
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* // DomainErrorType usage:
|
|
323
|
+
* const errorType: DomainErrorType = 'DOMAIN.INVALID_STATE';
|
|
312
324
|
*/
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
325
|
+
type DomainErrorType = 'DOMAIN.INVALID_STATE' | 'DOMAIN.INVALID_VALUE';
|
|
326
|
+
type ValueOf<T> = T[keyof T];
|
|
327
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
328
|
+
/**
|
|
329
|
+
* Interface for declaring domain error namespaces via declaration merging.
|
|
330
|
+
* Projects extend this interface to add their own namespaces and error codes.
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* // In your project's type definition file (.d.ts):
|
|
334
|
+
* declare module '@your-org/domain-errors' {
|
|
335
|
+
* interface DomainErrorNamespaces {
|
|
336
|
+
* USER: ['NOT_FOUND', 'INVALID_EMAIL', 'SUSPENDED'];
|
|
337
|
+
* ORDER: ['NOT_FOUND', 'INVALID_STATUS', 'OUT_OF_STOCK'];
|
|
338
|
+
* // Override default namespace (optional):
|
|
339
|
+
* CORE: ['INTERNAL_ERROR', 'VALIDATION_FAILED', 'CONFIGURATION_ERROR'];
|
|
340
|
+
* }
|
|
341
|
+
* }
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* // This enables type-safe error codes:
|
|
345
|
+
* const code: DomainErrorCode = 'USER.NOT_FOUND'; // ✅ Valid
|
|
346
|
+
* const code: DomainErrorCode = 'USER.INVALID'; // ❌ TypeScript error
|
|
347
|
+
*/
|
|
348
|
+
interface DomainErrorNamespaces {
|
|
349
|
+
DOMAIN: ['INVALID_VALUE', 'INVALID_STATE'];
|
|
350
|
+
CORE: [
|
|
351
|
+
'INTERNAL_ERROR',
|
|
352
|
+
'VALIDATION_FAILED',
|
|
353
|
+
'CONFIGURATION_ERROR',
|
|
354
|
+
'NOT_IMPLEMENTED'
|
|
355
|
+
];
|
|
356
|
+
SYSTEM: ['UNEXPECTED', 'TIMEOUT', 'NETWORK_ERROR', 'DEPENDENCY_ERROR'];
|
|
319
357
|
}
|
|
320
|
-
|
|
358
|
+
type Namespace = keyof DomainErrorNamespaces;
|
|
359
|
+
type ErrorName<N extends Namespace> = DomainErrorNamespaces[N][number];
|
|
321
360
|
/**
|
|
322
|
-
*
|
|
361
|
+
* Union type of all valid domain error codes derived from registered namespaces.
|
|
362
|
+
* Automatically updates when projects extend DomainErrorNamespaces.
|
|
323
363
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
364
|
+
* @example
|
|
365
|
+
* // After extending DomainErrorNamespaces with USER namespace:
|
|
366
|
+
* type ErrorCode = DomainErrorCode;
|
|
367
|
+
* // Becomes: 'CORE.INTERNAL_ERROR' | 'CORE.VALIDATION_FAILED' |
|
|
368
|
+
* // 'USER.NOT_FOUND' | 'USER.INVALID_EMAIL' | ...
|
|
369
|
+
*/
|
|
370
|
+
type DomainErrorCode = {
|
|
371
|
+
[N in Namespace]: `${Uppercase<string & N>}.${Uppercase<ErrorName<N>>}`;
|
|
372
|
+
}[Namespace];
|
|
373
|
+
type ExtractNamespace<Code extends DomainErrorCode> = Code extends `${infer N}.${string}` ? N : never;
|
|
374
|
+
type ExtractErrorName<Code extends DomainErrorCode> = Code extends `${string}.${infer E}` ? E : never;
|
|
375
|
+
/**
|
|
376
|
+
* Base class for all domain errors in a Domain-Driven Design architecture.
|
|
377
|
+
*
|
|
378
|
+
* Domain errors represent violations of business rules and domain invariants.
|
|
379
|
+
* They are pure value objects without infrastructure concerns like IDs or timestamps.
|
|
380
|
+
*
|
|
381
|
+
* @typeParam Code - The specific error code from DomainErrorCode union
|
|
382
|
+
* @typeParam Meta - Type of metadata associated with this error
|
|
327
383
|
*
|
|
328
384
|
* @example
|
|
329
|
-
* //
|
|
330
|
-
*
|
|
385
|
+
* // 1. First, declare your namespaces:
|
|
386
|
+
* declare module '@your-org/domain-errors' {
|
|
387
|
+
* interface DomainErrorNamespaces {
|
|
388
|
+
* USER: ['NOT_FOUND', 'INVALID_EMAIL'];
|
|
389
|
+
* }
|
|
390
|
+
* }
|
|
331
391
|
*
|
|
332
392
|
* @example
|
|
333
|
-
* // Create
|
|
334
|
-
*
|
|
393
|
+
* // 2. Create a concrete domain error:
|
|
394
|
+
* class UserNotFoundError extends DomainError<'USER.NOT_FOUND', { userId: string }> {
|
|
395
|
+
* public readonly code = 'USER.NOT_FOUND' as const;
|
|
396
|
+
* public readonly type: DomainErrorType = 'DOMAIN.INVALID_VALUE';
|
|
335
397
|
*
|
|
336
|
-
*
|
|
398
|
+
* constructor(userId: string) {
|
|
399
|
+
* super(`User with ID '${userId}' not found`, { userId });
|
|
400
|
+
* }
|
|
401
|
+
* }
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* // 3. Usage in domain services:
|
|
405
|
+
* class UserService {
|
|
406
|
+
* async activateUser(userId: string): Promise<Result<User, DomainError>> {
|
|
407
|
+
* const user = await this.repository.findById(userId);
|
|
408
|
+
*
|
|
409
|
+
* if (!user) {
|
|
410
|
+
* return Result.failure(new UserNotFoundError(userId));
|
|
411
|
+
* }
|
|
412
|
+
*
|
|
413
|
+
* if (user.isSuspended) {
|
|
414
|
+
* return Result.failure(new UserSuspendedError(userId));
|
|
415
|
+
* }
|
|
416
|
+
*
|
|
417
|
+
* // Business logic...
|
|
418
|
+
* }
|
|
419
|
+
* }
|
|
420
|
+
*
|
|
421
|
+
* @abstract
|
|
337
422
|
*/
|
|
338
|
-
declare class
|
|
339
|
-
private static readonly schema;
|
|
340
|
-
constructor(value: string);
|
|
423
|
+
declare abstract class DomainError<Meta extends Record<string, Primitive> = EmptyObject, Code extends DomainErrorCode = DomainErrorCode> {
|
|
341
424
|
/**
|
|
342
|
-
*
|
|
343
|
-
* Use only for untrusted input.
|
|
425
|
+
* Machine-readable error code in format: NAMESPACE.ERROR_NAME
|
|
344
426
|
*
|
|
345
|
-
* @
|
|
427
|
+
* @remarks
|
|
428
|
+
* - Must be uppercase (e.g., 'USER.NOT_FOUND')
|
|
429
|
+
* - Namespace must be declared in DomainErrorNamespaces
|
|
430
|
+
* - Error name must be in the namespace's array
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* public readonly code = 'USER.NOT_FOUND' as const;
|
|
346
434
|
*/
|
|
347
|
-
|
|
435
|
+
abstract readonly code: Code;
|
|
348
436
|
/**
|
|
349
|
-
*
|
|
437
|
+
* Human-readable error message describing the domain rule violation.
|
|
438
|
+
* Should be meaningful to developers and potentially end-users.
|
|
439
|
+
*
|
|
440
|
+
* @remarks
|
|
441
|
+
* - Avoid technical implementation details
|
|
442
|
+
* - Focus on the business rule that was violated
|
|
443
|
+
* - Can include values from metadata for context
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* // Good: "Order amount $150 exceeds maximum limit of $100"
|
|
447
|
+
* // Bad: "Amount validation failed: 150 > 100"
|
|
350
448
|
*/
|
|
351
|
-
|
|
352
|
-
|
|
449
|
+
readonly message: string;
|
|
450
|
+
/**
|
|
451
|
+
* Immutable structured context providing additional information about the error.
|
|
452
|
+
* Useful for debugging, logging, and creating detailed error messages.
|
|
453
|
+
*
|
|
454
|
+
* @remarks
|
|
455
|
+
* - Values must be primitive types (string, number, boolean, etc.)
|
|
456
|
+
* - Object is frozen to prevent mutation
|
|
457
|
+
* - Type-safe based on the error code
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* // For UserNotFoundError:
|
|
461
|
+
* { userId: 'usr_123', attemptedAction: 'activate' }
|
|
462
|
+
*
|
|
463
|
+
* @readonly
|
|
464
|
+
*/
|
|
465
|
+
readonly metadata: Readonly<Meta>;
|
|
466
|
+
/**
|
|
467
|
+
* Category of domain error indicating the nature of violation.
|
|
468
|
+
*
|
|
469
|
+
* @remarks
|
|
470
|
+
* Use 'DOMAIN.INVALID_STATE' for state violations (e.g., "Cannot checkout empty cart")
|
|
471
|
+
* Use 'DOMAIN.INVALID_VALUE' for value violations (e.g., "Email format is invalid")
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* public readonly type: DomainErrorType = 'DOMAIN.INVALID_VALUE';
|
|
475
|
+
*/
|
|
476
|
+
abstract readonly type: DomainErrorType;
|
|
477
|
+
/** Get error name from error code */
|
|
478
|
+
get errorName(): ExtractErrorName<Code>;
|
|
479
|
+
/** Get namespace from error code */
|
|
480
|
+
get namespace(): ExtractNamespace<Code>;
|
|
481
|
+
/**
|
|
482
|
+
* Creates a new DomainError instance.
|
|
483
|
+
*
|
|
484
|
+
* @param message - Human-readable description of the domain rule violation
|
|
485
|
+
* @param metadata - Optional structured context (primitive values only)
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* constructor(userId: string) {
|
|
489
|
+
* super(`User with ID '${userId}' not found`, { userId });
|
|
490
|
+
* }
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* constructor(amount: number, maxLimit: number) {
|
|
494
|
+
* super(
|
|
495
|
+
* `Order amount $${amount} exceeds maximum limit of $${maxLimit}`,
|
|
496
|
+
* { amount, maxLimit }
|
|
497
|
+
* );
|
|
498
|
+
* }
|
|
499
|
+
*/
|
|
500
|
+
protected constructor(message: string, ...args: keyof Meta extends never ? [] | [metadata?: Meta] : [metadata: Meta]);
|
|
501
|
+
/**
|
|
502
|
+
* Serializes the error to a plain object for debugging, logging, or transport.
|
|
503
|
+
* Does not include infrastructure concerns like stack traces or timestamps.
|
|
504
|
+
*
|
|
505
|
+
* @returns Plain object with error details
|
|
506
|
+
*
|
|
507
|
+
* @example
|
|
508
|
+
* const error = new UserNotFoundError('usr_123');
|
|
509
|
+
* const json = error.toJSON();
|
|
510
|
+
* // Result:
|
|
511
|
+
* // {
|
|
512
|
+
* // code: 'USER.NOT_FOUND',
|
|
513
|
+
* // message: "User with ID 'usr_123' not found",
|
|
514
|
+
* // type: 'DOMAIN.INVALID_VALUE',
|
|
515
|
+
* // metadata: { userId: 'usr_123' }
|
|
516
|
+
* // }
|
|
517
|
+
*/
|
|
518
|
+
toObject(): {
|
|
519
|
+
metadata: Readonly<Meta>;
|
|
520
|
+
message: string;
|
|
521
|
+
code: Code;
|
|
522
|
+
type: DomainErrorType;
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* Returns a string representation of the error.
|
|
526
|
+
* Format: [CODE] MESSAGE
|
|
527
|
+
*
|
|
528
|
+
* @returns Human-readable string representation
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* const error = new UserNotFoundError('usr_123');
|
|
532
|
+
* console.log(error.toString());
|
|
533
|
+
* // Output: [USER.NOT_FOUND] User with ID 'usr_123' not found
|
|
534
|
+
*
|
|
535
|
+
* @override
|
|
536
|
+
*/
|
|
537
|
+
toString(): string;
|
|
353
538
|
}
|
|
354
539
|
|
|
355
540
|
/**
|
|
356
|
-
*
|
|
541
|
+
* Default domain errors for common scenarios.
|
|
542
|
+
* These errors are available across all projects using this module.
|
|
543
|
+
*/
|
|
544
|
+
/**
|
|
545
|
+
* Error thrown when an unexpected internal error occurs.
|
|
546
|
+
* Typically used for programming bugs, invalid assumptions, or states that should never happen.
|
|
357
547
|
*
|
|
358
|
-
*
|
|
359
|
-
* -
|
|
360
|
-
* -
|
|
548
|
+
* @remarks
|
|
549
|
+
* - Metadata is optional for empty metadata types and required if a non-empty type is provided.
|
|
550
|
+
* - Useful for debugging, logging, and adding context to unexpected failures.
|
|
551
|
+
*
|
|
552
|
+
* @template T - Type of metadata object (must extend Record<string, Primitive>)
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* // Catch a programming error:
|
|
556
|
+
* try {
|
|
557
|
+
* complexBusinessLogic();
|
|
558
|
+
* } catch (error) {
|
|
559
|
+
* throw new InternalError(
|
|
560
|
+
* 'Unexpected error in complexBusinessLogic',
|
|
561
|
+
* { originalError: error.message, timestamp: Date.now() }
|
|
562
|
+
* );
|
|
563
|
+
* }
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* // Fallback for unhandled cases:
|
|
567
|
+
* switch (status) {
|
|
568
|
+
* case 'PENDING':
|
|
569
|
+
* break;
|
|
570
|
+
* case 'COMPLETED':
|
|
571
|
+
* break;
|
|
572
|
+
* default:
|
|
573
|
+
* throw new InternalError(
|
|
574
|
+
* `Unhandled status: ${status}`,
|
|
575
|
+
* { status }
|
|
576
|
+
* );
|
|
577
|
+
* }
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* // With custom metadata type:
|
|
581
|
+
* type ErrorMetadata = { userId: string; action: string };
|
|
582
|
+
* throw new InternalError<ErrorMetadata>(
|
|
583
|
+
* 'Failed to process user action',
|
|
584
|
+
* { userId: 'usr_123', action: 'activate' }
|
|
585
|
+
* );
|
|
361
586
|
*/
|
|
362
|
-
declare class
|
|
587
|
+
declare class InternalError<T extends Record<string, Primitive> = Record<string, Primitive>> extends DomainError<Metadata<T>> {
|
|
588
|
+
/** @inheritdoc */
|
|
589
|
+
readonly code: "CORE.INTERNAL_ERROR";
|
|
590
|
+
/** @inheritdoc */
|
|
591
|
+
readonly type: DomainErrorType;
|
|
592
|
+
/**
|
|
593
|
+
* Creates a new InternalError.
|
|
594
|
+
*
|
|
595
|
+
* @param message - Description of the internal error (defaults to 'An unexpected internal error occurred')
|
|
596
|
+
* @param metadata - Optional debug information (primitive values only)
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* // Basic usage:
|
|
600
|
+
* throw new InternalError('Something went wrong');
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* // With metadata:
|
|
604
|
+
* throw new InternalError(
|
|
605
|
+
* 'Database connection failed',
|
|
606
|
+
* { host: 'localhost', port: 5432, retries: 3 }
|
|
607
|
+
* );
|
|
608
|
+
*/
|
|
609
|
+
constructor(message?: string, metadata?: Metadata<T>);
|
|
363
610
|
}
|
|
364
611
|
|
|
365
612
|
/**
|
|
366
|
-
*
|
|
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.
|
|
613
|
+
* Error thrown when an entity or aggregate is in an invalid state for the requested operation.
|
|
614
|
+
* Use for state violations (e.g., "Cannot checkout empty cart", "Cannot cancel completed order").
|
|
370
615
|
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
* ```ts
|
|
377
|
-
* import { HttpStatus } from 'path-to-this-file';
|
|
616
|
+
* @example
|
|
617
|
+
* // Prevent invalid state transitions:
|
|
618
|
+
* if (order.status === 'COMPLETED') {
|
|
619
|
+
* throw new InvalidStateError('Cannot cancel a completed order');
|
|
620
|
+
* }
|
|
378
621
|
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
* };
|
|
622
|
+
* @example
|
|
623
|
+
* // With context:
|
|
624
|
+
* if (!cart.hasItems()) {
|
|
625
|
+
* throw new InvalidStateError('Cannot checkout empty cart');
|
|
384
626
|
* }
|
|
385
|
-
* ```
|
|
386
627
|
*/
|
|
387
|
-
declare
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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;
|
|
628
|
+
declare class InvalidStateError extends DomainError<Record<string, Primitive>> {
|
|
629
|
+
code: DomainErrorCode;
|
|
630
|
+
type: DomainErrorType;
|
|
631
|
+
constructor(message?: string);
|
|
632
|
+
}
|
|
633
|
+
|
|
453
634
|
/**
|
|
454
|
-
*
|
|
455
|
-
* Use for
|
|
635
|
+
* Error thrown when a value violates domain rules or constraints.
|
|
636
|
+
* Use for value violations (e.g., "Email format is invalid", "Age cannot be negative").
|
|
637
|
+
*
|
|
638
|
+
* @template T - Type of metadata object (must extend Record<string, Primitive>)
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* // Basic usage:
|
|
642
|
+
* if (age < 0) {
|
|
643
|
+
* throw new InvalidValueError('Age cannot be negative');
|
|
644
|
+
* }
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* // With metadata:
|
|
648
|
+
* if (!isValidEmail(email)) {
|
|
649
|
+
* throw new InvalidValueError(
|
|
650
|
+
* 'Invalid email format',
|
|
651
|
+
* { email, pattern: '^[^@]+@[^@]+\\.[^@]+$' }
|
|
652
|
+
* );
|
|
653
|
+
* }
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* // With custom metadata type:
|
|
657
|
+
* type ValidationMetadata = { field: string; value: unknown; reason: string };
|
|
658
|
+
* throw new InvalidValueError<ValidationMetadata>(
|
|
659
|
+
* 'Validation failed',
|
|
660
|
+
* { field: 'email', value: email, reason: 'Invalid format' }
|
|
661
|
+
* );
|
|
456
662
|
*/
|
|
457
|
-
declare
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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];
|
|
663
|
+
declare class InvalidValueError<T extends Record<string, Primitive> = Record<string, Primitive>> extends DomainError<Metadata<T>> {
|
|
664
|
+
code: DomainErrorCode;
|
|
665
|
+
type: DomainErrorType;
|
|
666
|
+
constructor(msg?: string, meta?: Metadata<T>);
|
|
667
|
+
}
|
|
523
668
|
|
|
524
669
|
/**
|
|
525
|
-
*
|
|
526
|
-
*
|
|
670
|
+
* Error thrown when an operation times out.
|
|
671
|
+
* Use for operations that exceed their allowed execution time.
|
|
672
|
+
*
|
|
673
|
+
* @template T - Type of metadata object (must extend Record<string, Primitive>)
|
|
674
|
+
*
|
|
675
|
+
* @example
|
|
676
|
+
* // Operation timeout:
|
|
677
|
+
* const timeout = setTimeout(() => {
|
|
678
|
+
* throw new TimeoutError(
|
|
679
|
+
* 'User registration timed out'
|
|
680
|
+
* );
|
|
681
|
+
* }, 5000);
|
|
682
|
+
*
|
|
683
|
+
* @example
|
|
684
|
+
* // With Promise.race:
|
|
685
|
+
* type Props = { url: string; timeoutMs: number };
|
|
686
|
+
* async function fetchWithTimeout(url: string, timeoutMs: number) {
|
|
687
|
+
* const timeoutPromise = new Promise<never>((_, reject) => {
|
|
688
|
+
* setTimeout(() => {
|
|
689
|
+
* reject(new TimeoutError<Props>(
|
|
690
|
+
* `Request to ${url} timed out`,
|
|
691
|
+
* { url, timeoutMs }
|
|
692
|
+
* ));
|
|
693
|
+
* }, timeoutMs);
|
|
694
|
+
* });
|
|
695
|
+
*
|
|
696
|
+
* return await Promise.race([fetch(url), timeoutPromise]);
|
|
697
|
+
* }
|
|
698
|
+
*
|
|
699
|
+
* @example
|
|
700
|
+
* // With custom metadata:
|
|
701
|
+
* throw new TimeoutError(
|
|
702
|
+
* 'Database query timed out',
|
|
703
|
+
* { query: 'SELECT * FROM users', timeout: 5000, retries: 3 }
|
|
704
|
+
* );
|
|
527
705
|
*/
|
|
528
|
-
declare
|
|
529
|
-
|
|
530
|
-
readonly
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
706
|
+
declare class TimeoutError<T extends Record<string, Primitive> = Record<string, Primitive>> extends DomainError<Metadata<T>> {
|
|
707
|
+
/** @inheritdoc */
|
|
708
|
+
readonly code: "SYSTEM.TIMEOUT";
|
|
709
|
+
/** @inheritdoc */
|
|
710
|
+
readonly type: DomainErrorType;
|
|
711
|
+
/**
|
|
712
|
+
* Creates a new TimeoutError.
|
|
713
|
+
*
|
|
714
|
+
* @param message - Description of the timeout
|
|
715
|
+
* @param metadata - Optional timeout context
|
|
716
|
+
*/
|
|
717
|
+
constructor(message: string, metadata?: Metadata<T>);
|
|
536
718
|
}
|
|
537
|
-
type DomainErrorCode = 'DOMAIN.INVALID_STATE' | 'DOMAIN.INVALID_VALUE';
|
|
538
719
|
|
|
539
720
|
/**
|
|
540
721
|
* Represents the result of an operation, which can be either a success or a failure.
|
|
@@ -877,65 +1058,308 @@ declare class Result<T, E> {
|
|
|
877
1058
|
isSuccessResult(): this is Result<T, never>;
|
|
878
1059
|
}
|
|
879
1060
|
|
|
1061
|
+
type ClockPort = {
|
|
1062
|
+
now: () => Date;
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
interface ExtraProps extends Record<string, Primitive> {
|
|
1066
|
+
entityId?: string;
|
|
1067
|
+
entityType?: string;
|
|
1068
|
+
}
|
|
1069
|
+
type Props$1 = Metadata<ExtraProps>;
|
|
1070
|
+
/**
|
|
1071
|
+
* Custom error class for entity validation failures.
|
|
1072
|
+
*/
|
|
1073
|
+
declare class EntityValidationError extends DomainError<Props$1> {
|
|
1074
|
+
code: DomainErrorCode;
|
|
1075
|
+
type: DomainErrorType;
|
|
1076
|
+
constructor(message: string, props: Props$1);
|
|
1077
|
+
static create(msg: string, props: Props$1): EntityValidationError;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
type Params = {
|
|
1081
|
+
value: string;
|
|
1082
|
+
};
|
|
1083
|
+
type Props = Metadata<Params>;
|
|
1084
|
+
declare class InvalidValueObjectError extends DomainError<Props> {
|
|
1085
|
+
code: DomainErrorCode;
|
|
1086
|
+
type: DomainErrorType;
|
|
1087
|
+
static create(msg?: string, meta?: Props): InvalidValueObjectError;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
880
1090
|
/**
|
|
881
|
-
*
|
|
1091
|
+
* Represents a UUID (Universally Unique Identifier) value object.
|
|
1092
|
+
*
|
|
1093
|
+
* This class extends PrimitiveValueObject to provide type-safe UUID handling
|
|
1094
|
+
* with validation using Zod schema. UUIDs are immutable and can be generated
|
|
1095
|
+
* randomly or created from string values.
|
|
1096
|
+
* @deprecated
|
|
1097
|
+
* @example
|
|
1098
|
+
* // Generate a new random UUID
|
|
1099
|
+
* const id = UUID.generate();
|
|
1100
|
+
*
|
|
1101
|
+
* @example
|
|
1102
|
+
* // Create UUID from an existing string
|
|
1103
|
+
* const id = UUID.fromString('550e8400-e29b-41d4-a716-446655440000');
|
|
882
1104
|
*
|
|
883
|
-
* @
|
|
884
|
-
* @param seen - A WeakSet to track already processed objects (for circular references).
|
|
885
|
-
* @returns A deeply frozen version of the input object.
|
|
1105
|
+
* @throws {InvalidValueObjectError} When the provided string is not a valid UUID format
|
|
886
1106
|
*/
|
|
887
|
-
declare
|
|
1107
|
+
declare class UUID extends PrimitiveValueObject<string> {
|
|
1108
|
+
private static readonly schema;
|
|
1109
|
+
constructor(value: string);
|
|
1110
|
+
/**
|
|
1111
|
+
* Creates an UUID from an external string.
|
|
1112
|
+
* Use only for untrusted input.
|
|
1113
|
+
*
|
|
1114
|
+
* @param value - UUID string
|
|
1115
|
+
*/
|
|
1116
|
+
static fromString(value: string): UUID;
|
|
1117
|
+
/**
|
|
1118
|
+
* Generates a new AggregateId.
|
|
1119
|
+
*/
|
|
1120
|
+
static generate<T extends typeof UUID>(this: T): InstanceType<T>;
|
|
1121
|
+
protected validate(value: string): void;
|
|
1122
|
+
}
|
|
888
1123
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1124
|
+
/**
|
|
1125
|
+
* AggregateId represents a strongly-typed aggregate identifier.
|
|
1126
|
+
*
|
|
1127
|
+
* - Backed by UUID v4
|
|
1128
|
+
* - Immutable
|
|
1129
|
+
* - Comparable only to AggregateId
|
|
1130
|
+
*/
|
|
1131
|
+
declare class AggregateId extends UUID {
|
|
896
1132
|
}
|
|
1133
|
+
|
|
897
1134
|
/**
|
|
898
|
-
*
|
|
1135
|
+
* UUID is a branded string type.
|
|
899
1136
|
*
|
|
900
|
-
*
|
|
901
|
-
*
|
|
1137
|
+
* - Prevents accidental use of arbitrary strings
|
|
1138
|
+
* - Requires explicit validation or construction
|
|
1139
|
+
* - Zero runtime cost after creation
|
|
1140
|
+
*/
|
|
1141
|
+
type UuID = Tagged<string, 'UUID'>;
|
|
1142
|
+
/**
|
|
1143
|
+
* Abstract base class for all domain identifiers.
|
|
902
1144
|
*
|
|
903
|
-
*
|
|
904
|
-
*
|
|
1145
|
+
* Responsibilities:
|
|
1146
|
+
* - Enforces that every DomainID wraps a primitive string value.
|
|
1147
|
+
* - Provides a type-safe static factory `fromString` for creating concrete IDs.
|
|
1148
|
+
* - Leaves domain-specific validation to subclasses via `validate`.
|
|
905
1149
|
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
*
|
|
913
|
-
*
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
1150
|
+
* Design Notes:
|
|
1151
|
+
* - The class cannot know how to validate the ID itself, because validation
|
|
1152
|
+
* rules differ between ID types (e.g., UUID v4, ULID, NanoID).
|
|
1153
|
+
* - Static factory uses `new this(value)` pattern; hence, base class is **not abstract** in TypeScript terms.
|
|
1154
|
+
* - Subclasses must implement `validate(value: string)` inherited from PrimitiveValueObject.
|
|
1155
|
+
*
|
|
1156
|
+
* Usage:
|
|
1157
|
+
* ```ts
|
|
1158
|
+
* class AuthAttemptId extends DomainID {
|
|
1159
|
+
* protected validate(value: string): void {
|
|
1160
|
+
* if (!isValidUuid(value)) {
|
|
1161
|
+
* throw new Error('Invalid AuthAttemptId');
|
|
1162
|
+
* }
|
|
917
1163
|
* }
|
|
918
1164
|
* }
|
|
1165
|
+
*
|
|
1166
|
+
* const id = AuthAttemptId.fromString('550e8400-e29b-41d4-a716-446655440000');
|
|
919
1167
|
* ```
|
|
920
1168
|
*/
|
|
921
|
-
declare abstract class
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1169
|
+
declare abstract class DomainID extends PrimitiveValueObject<UuID> {
|
|
1170
|
+
static schema: z.ZodUUID;
|
|
1171
|
+
constructor(value: string);
|
|
1172
|
+
/**
|
|
1173
|
+
* Creates a concrete DomainID from a trusted string value.
|
|
1174
|
+
*
|
|
1175
|
+
* - Validation is enforced in the subclass constructor.
|
|
1176
|
+
* - Intended for application or infrastructure layers to produce IDs.
|
|
1177
|
+
*
|
|
1178
|
+
* @template T - Concrete subclass of DomainID
|
|
1179
|
+
* @param this - The constructor of the concrete subclass
|
|
1180
|
+
* @param value - Raw string value of the ID
|
|
1181
|
+
* @returns Instance of the concrete DomainID subclass
|
|
1182
|
+
*/
|
|
1183
|
+
static fromString<T extends new (value: string) => DomainID>(this: T, value: string): InstanceType<T>;
|
|
1184
|
+
static generate<T extends new (value: string) => DomainID>(this: T): InstanceType<T>;
|
|
1185
|
+
protected validate(value: UuID): void;
|
|
933
1186
|
}
|
|
934
1187
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1188
|
+
declare class Email extends PrimitiveValueObject<string> {
|
|
1189
|
+
private static readonly schema;
|
|
1190
|
+
constructor(value: string);
|
|
1191
|
+
static fromString(value: string): Email;
|
|
1192
|
+
protected validate(value: string): void;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* HTTP status code catalog.
|
|
1197
|
+
*
|
|
1198
|
+
* This object provides a typed, immutable map of standard HTTP status names
|
|
1199
|
+
* to their numeric codes. Designed for server-side frameworks such as Fastify.
|
|
1200
|
+
*
|
|
1201
|
+
* - All identifiers use clear, canonical semantic names.
|
|
1202
|
+
* - Values are numeric status codes.
|
|
1203
|
+
* - Frozen to prevent runtime mutation.
|
|
1204
|
+
* - Exporting `HttpStatus` ensures type-safe usage across the codebase.
|
|
1205
|
+
* Usage:
|
|
1206
|
+
* ```ts
|
|
1207
|
+
* import { HttpStatus } from 'path-to-this-file';
|
|
1208
|
+
*
|
|
1209
|
+
* function handleRequest() {
|
|
1210
|
+
* return {
|
|
1211
|
+
* statusCode: HttpStatus.OK,
|
|
1212
|
+
* body: 'Success',
|
|
1213
|
+
* };
|
|
1214
|
+
* }
|
|
1215
|
+
* ```
|
|
1216
|
+
*/
|
|
1217
|
+
declare const HttpStatus: Readonly<{
|
|
1218
|
+
readonly REQUEST_HEADER_FIELDS_TOO_LARGE: 431;
|
|
1219
|
+
readonly NETWORK_AUTHENTICATION_REQUIRED: 511;
|
|
1220
|
+
readonly NON_AUTHORITATIVE_INFORMATION: 203;
|
|
1221
|
+
readonly PROXY_AUTHENTICATION_REQUIRED: 407;
|
|
1222
|
+
readonly UNAVAILABLE_FOR_LEGAL_REASONS: 451;
|
|
1223
|
+
readonly HTTP_VERSION_NOT_SUPPORTED: 505;
|
|
1224
|
+
readonly BANDWIDTH_LIMIT_EXCEEDED: 509;
|
|
1225
|
+
readonly VARIANT_ALSO_NEGOTIATES: 506;
|
|
1226
|
+
readonly UNSUPPORTED_MEDIA_TYPE: 415;
|
|
1227
|
+
readonly RANGE_NOT_SATISFIABLE: 416;
|
|
1228
|
+
readonly PRECONDITION_REQUIRED: 428;
|
|
1229
|
+
readonly INTERNAL_SERVER_ERROR: 500;
|
|
1230
|
+
readonly UNPROCESSABLE_ENTITY: 422;
|
|
1231
|
+
readonly INSUFFICIENT_STORAGE: 507;
|
|
1232
|
+
readonly SWITCHING_PROTOCOLS: 101;
|
|
1233
|
+
readonly PRECONDITION_FAILED: 412;
|
|
1234
|
+
readonly MISDIRECTED_REQUEST: 421;
|
|
1235
|
+
readonly SERVICE_UNAVAILABLE: 503;
|
|
1236
|
+
readonly TEMPORARY_REDIRECT: 307;
|
|
1237
|
+
readonly PERMANENT_REDIRECT: 308;
|
|
1238
|
+
readonly METHOD_NOT_ALLOWED: 405;
|
|
1239
|
+
readonly EXPECTATION_FAILED: 417;
|
|
1240
|
+
readonly MOVED_PERMANENTLY: 301;
|
|
1241
|
+
readonly PAYLOAD_TOO_LARGE: 413;
|
|
1242
|
+
readonly FAILED_DEPENDENCY: 424;
|
|
1243
|
+
readonly TOO_MANY_REQUESTS: 429;
|
|
1244
|
+
readonly ALREADY_REPORTED: 208;
|
|
1245
|
+
readonly MULTIPLE_CHOICES: 300;
|
|
1246
|
+
readonly PAYMENT_REQUIRED: 402;
|
|
1247
|
+
readonly UPGRADE_REQUIRED: 426;
|
|
1248
|
+
readonly PARTIAL_CONTENT: 206;
|
|
1249
|
+
readonly REQUEST_TIMEOUT: 408;
|
|
1250
|
+
readonly LENGTH_REQUIRED: 411;
|
|
1251
|
+
readonly NOT_IMPLEMENTED: 501;
|
|
1252
|
+
readonly GATEWAY_TIMEOUT: 504;
|
|
1253
|
+
readonly NOT_ACCEPTABLE: 406;
|
|
1254
|
+
readonly RESET_CONTENT: 205;
|
|
1255
|
+
readonly LOOP_DETECTED: 508;
|
|
1256
|
+
readonly MULTI_STATUS: 207;
|
|
1257
|
+
readonly NOT_MODIFIED: 304;
|
|
1258
|
+
readonly UNAUTHORIZED: 401;
|
|
1259
|
+
readonly URI_TOO_LONG: 414;
|
|
1260
|
+
readonly NOT_EXTENDED: 510;
|
|
1261
|
+
readonly EARLY_HINTS: 103;
|
|
1262
|
+
readonly BAD_REQUEST: 400;
|
|
1263
|
+
readonly IM_A_TEAPOT: 418;
|
|
1264
|
+
readonly BAD_GATEWAY: 502;
|
|
1265
|
+
readonly PROCESSING: 102;
|
|
1266
|
+
readonly NO_CONTENT: 204;
|
|
1267
|
+
readonly SEE_OTHER: 303;
|
|
1268
|
+
readonly USE_PROXY: 305;
|
|
1269
|
+
readonly FORBIDDEN: 403;
|
|
1270
|
+
readonly NOT_FOUND: 404;
|
|
1271
|
+
readonly TOO_EARLY: 425;
|
|
1272
|
+
readonly CONTINUE: 100;
|
|
1273
|
+
readonly ACCEPTED: 202;
|
|
1274
|
+
readonly CONFLICT: 409;
|
|
1275
|
+
readonly CREATED: 201;
|
|
1276
|
+
readonly IM_USED: 226;
|
|
1277
|
+
readonly LOCKED: 423;
|
|
1278
|
+
readonly FOUND: 302;
|
|
1279
|
+
readonly GONE: 410;
|
|
1280
|
+
readonly OK: 200;
|
|
1281
|
+
}>;
|
|
1282
|
+
type HttpStatusCode = keyof typeof HttpStatusMessage;
|
|
1283
|
+
/**
|
|
1284
|
+
* HTTP status messages mapped by numeric code.
|
|
1285
|
+
* Use for sending descriptive text in responses or logging.
|
|
1286
|
+
*/
|
|
1287
|
+
declare const HttpStatusMessage: {
|
|
1288
|
+
readonly 431: "Request Header Fields Too Large";
|
|
1289
|
+
readonly 511: "Network Authentication Required";
|
|
1290
|
+
readonly 203: "Non-Authoritative Information";
|
|
1291
|
+
readonly 407: "Proxy Authentication Required";
|
|
1292
|
+
readonly 451: "Unavailable For Legal Reasons";
|
|
1293
|
+
readonly 505: "HTTP Version Not Supported";
|
|
1294
|
+
readonly 509: "Bandwidth Limit Exceeded";
|
|
1295
|
+
readonly 506: "Variant Also Negotiates";
|
|
1296
|
+
readonly 415: "Unsupported Media Type";
|
|
1297
|
+
readonly 416: "Range Not Satisfiable";
|
|
1298
|
+
readonly 428: "Precondition Required";
|
|
1299
|
+
readonly 500: "Internal Server Error";
|
|
1300
|
+
readonly 422: "Unprocessable Entity";
|
|
1301
|
+
readonly 507: "Insufficient Storage";
|
|
1302
|
+
readonly 101: "Switching Protocols";
|
|
1303
|
+
readonly 412: "Precondition Failed";
|
|
1304
|
+
readonly 421: "Misdirected Request";
|
|
1305
|
+
readonly 503: "Service Unavailable";
|
|
1306
|
+
readonly 307: "Temporary Redirect";
|
|
1307
|
+
readonly 308: "Permanent Redirect";
|
|
1308
|
+
readonly 405: "Method Not Allowed";
|
|
1309
|
+
readonly 417: "Expectation Failed";
|
|
1310
|
+
readonly 301: "Moved Permanently";
|
|
1311
|
+
readonly 413: "Payload Too Large";
|
|
1312
|
+
readonly 424: "Failed Dependency";
|
|
1313
|
+
readonly 429: "Too Many Requests";
|
|
1314
|
+
readonly 208: "Already Reported";
|
|
1315
|
+
readonly 300: "Multiple Choices";
|
|
1316
|
+
readonly 402: "Payment Required";
|
|
1317
|
+
readonly 426: "Upgrade Required";
|
|
1318
|
+
readonly 206: "Partial Content";
|
|
1319
|
+
readonly 408: "Request Timeout";
|
|
1320
|
+
readonly 411: "Length Required";
|
|
1321
|
+
readonly 501: "Not Implemented";
|
|
1322
|
+
readonly 504: "Gateway Timeout";
|
|
1323
|
+
readonly 406: "Not Acceptable";
|
|
1324
|
+
readonly 205: "Reset Content";
|
|
1325
|
+
readonly 508: "Loop Detected";
|
|
1326
|
+
readonly 207: "Multi-Status";
|
|
1327
|
+
readonly 304: "Not Modified";
|
|
1328
|
+
readonly 401: "Unauthorized";
|
|
1329
|
+
readonly 414: "URI Too Long";
|
|
1330
|
+
readonly 418: "I'm a Teapot";
|
|
1331
|
+
readonly 510: "Not Extended";
|
|
1332
|
+
readonly 103: "Early Hints";
|
|
1333
|
+
readonly 400: "Bad Request";
|
|
1334
|
+
readonly 502: "Bad Gateway";
|
|
1335
|
+
readonly 102: "Processing";
|
|
1336
|
+
readonly 204: "No Content";
|
|
1337
|
+
readonly 303: "See Other";
|
|
1338
|
+
readonly 305: "Use Proxy";
|
|
1339
|
+
readonly 403: "Forbidden";
|
|
1340
|
+
readonly 404: "Not Found";
|
|
1341
|
+
readonly 425: "Too Early";
|
|
1342
|
+
readonly 100: "Continue";
|
|
1343
|
+
readonly 202: "Accepted";
|
|
1344
|
+
readonly 226: "I'm Used";
|
|
1345
|
+
readonly 409: "Conflict";
|
|
1346
|
+
readonly 201: "Created";
|
|
1347
|
+
readonly 423: "Locked";
|
|
1348
|
+
readonly 302: "Found";
|
|
1349
|
+
readonly 410: "Gone";
|
|
1350
|
+
readonly 200: "OK";
|
|
1351
|
+
};
|
|
1352
|
+
type HttpStatusMessage = (typeof HttpStatusMessage)[keyof typeof HttpStatusMessage];
|
|
1353
|
+
|
|
1354
|
+
/**
|
|
1355
|
+
* Deeply freezes an object graph to enforce runtime immutability.
|
|
1356
|
+
* - Handles arrays
|
|
1357
|
+
* - Handles circular references
|
|
1358
|
+
* - Skips functions
|
|
1359
|
+
* - Skips already frozen objects
|
|
1360
|
+
*
|
|
1361
|
+
* Intended for aggregate and value object state only.
|
|
1362
|
+
*/
|
|
1363
|
+
declare function deepFreeze<T>(value: T, seen?: WeakSet<object>): Readonly<T>;
|
|
940
1364
|
|
|
941
|
-
export { AggregateId, AggregateRoot, ApplicationError, type ApplicationServicePort,
|
|
1365
|
+
export { AggregateId, AggregateRoot, ApplicationError, type ApplicationServicePort, type ClockPort, 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, InvalidStateError, InvalidValueError, InvalidValueObjectError, type Metadata, type Primitive, PrimitiveValueObject, Result, TimeoutError, UUID, type UnionToIntersection, type UnixTimestampMillis, type UuID, ValueObject, type ValueOf, deepFreeze };
|