@rineex/ddd 0.1.0 → 1.1.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
@@ -27,47 +27,6 @@ interface ApplicationServicePort<I, O> {
27
27
  execute: (args: I) => Promise<O>;
28
28
  }
29
29
 
30
- interface DomainErrorMetadata {
31
- cause?: {
32
- name: string;
33
- message: string;
34
- stack?: string;
35
- };
36
- [key: string]: unknown;
37
- }
38
- /**
39
- * Base class for all Domain Errors in the application.
40
- *
41
- * This class ensures:
42
- * 1. Serializable and structured for logs or API responses.
43
- * 2. Identifiable via stable error codes (not just class names).
44
- * 3. Contextual with optional structured metadata.
45
- * 4. Supports error chaining (cause) and stack trace preservation.
46
- *
47
- * @example
48
- * export class InsufficientFundsError extends DomainError {
49
- * constructor(accountId: string, currentBalance: number) {
50
- * super(
51
- * `Account ${accountId} has insufficient funds.`,
52
- * 'INSUFFICIENT_FUNDS',
53
- * { accountId, currentBalance }
54
- * );
55
- * }
56
- * }
57
- */
58
- declare abstract class DomainError extends Error {
59
- /** Stable, machine-readable error code */
60
- readonly code: string;
61
- /** Structured, immutable domain metadata */
62
- readonly metadata: Readonly<DomainErrorMetadata>;
63
- /**
64
- * @param message - Human-readable error message
65
- * @param code - Stable error code
66
- * @param metadata - Domain-specific structured data; optional `cause` can be included
67
- */
68
- protected constructor(message: string, code: string, metadata?: DomainErrorMetadata);
69
- }
70
-
71
30
  declare abstract class ValueObject<T> {
72
31
  get value(): T;
73
32
  protected readonly props: Readonly<T>;
@@ -91,17 +50,6 @@ declare abstract class ValueObject<T> {
91
50
  protected abstract validate(props: T): void;
92
51
  }
93
52
 
94
- /**
95
- * Custom error class for entity validation failures.
96
- */
97
- declare class EntityValidationError extends DomainError {
98
- constructor(message: string, cause?: Error);
99
- }
100
-
101
- declare class InvalidValueObjectError extends DomainError {
102
- constructor(message: string);
103
- }
104
-
105
53
  /**
106
54
  * AggregateId is a ValueObject that represents a unique identifier for an aggregate.
107
55
  */
@@ -143,6 +91,15 @@ declare class AggregateId extends ValueObject<string> {
143
91
  protected validate(value: string): void;
144
92
  }
145
93
 
94
+ /**
95
+ * Utility to deeply freeze objects to ensure immutability - handles nested objects and arrays.
96
+ *
97
+ * @param obj - The object to be deeply frozen.
98
+ * @param seen - A WeakSet to track already processed objects (for circular references).
99
+ * @returns A deeply frozen version of the input object.
100
+ */
101
+ declare function deepFreeze<T>(obj: T, seen?: WeakSet<object>): Readonly<T>;
102
+
146
103
  /**
147
104
  * HTTP status code catalog.
148
105
  *
@@ -153,6 +110,17 @@ declare class AggregateId extends ValueObject<string> {
153
110
  * - Values are numeric status codes.
154
111
  * - Frozen to prevent runtime mutation.
155
112
  * - Exporting `HttpStatus` ensures type-safe usage across the codebase.
113
+ * Usage:
114
+ * ```ts
115
+ * import { HttpStatus } from 'path-to-this-file';
116
+ *
117
+ * function handleRequest() {
118
+ * return {
119
+ * statusCode: HttpStatus.OK,
120
+ * body: 'Success',
121
+ * };
122
+ * }
123
+ * ```
156
124
  */
157
125
  declare const HttpStatus: Readonly<{
158
126
  readonly REQUEST_HEADER_FIELDS_TOO_LARGE: 431;
@@ -291,15 +259,6 @@ declare const HttpStatusMessage: {
291
259
  };
292
260
  type HttpStatusMessage = (typeof HttpStatusMessage)[keyof typeof HttpStatusMessage];
293
261
 
294
- /**
295
- * Utility to deeply freeze objects to ensure immutability - handles nested objects and arrays.
296
- *
297
- * @param obj - The object to be deeply frozen.
298
- * @param seen - A WeakSet to track already processed objects (for circular references).
299
- * @returns A deeply frozen version of the input object
300
- */
301
- declare function deepFreeze<T>(obj: T, seen?: WeakSet<object>): Readonly<T>;
302
-
303
262
  interface ErrorParams {
304
263
  message: string;
305
264
  code: HttpStatusMessage;
@@ -352,4 +311,245 @@ type UnwrapValueObject<T> = T extends ValueObject<infer V> ? UnwrapValueObject<V
352
311
  declare function unwrapValueObject<T>(input: T, seen?: WeakSet<object>): UnwrapValueObject<T>;
353
312
  declare function ensureObject<T>(input: UnwrapValueObject<T>): object;
354
313
 
355
- export { AggregateId, ApplicationError, type ApplicationServicePort, DomainError, type DomainErrorMetadata, EntityValidationError, HttpStatus, type HttpStatusCode, HttpStatusMessage, InvalidValueObjectError, type UnwrapValueObject, ValueObject, deepFreeze, ensureObject, unwrapValueObject };
314
+ interface EntityBaseInterface {
315
+ id: AggregateId;
316
+ equals: (entity: unknown) => boolean;
317
+ }
318
+ /**
319
+ * Base properties common to all entities, including ID and timestamps.
320
+ */
321
+ interface BaseEntityProps {
322
+ /** Unique identifier for the entity */
323
+ id?: AggregateId;
324
+ /** Date when the entity was created */
325
+ createdAt: Date;
326
+ }
327
+ /**
328
+ * Interface for constructing an entity with optional timestamps.
329
+ * @template Props - The specific props type for the entity
330
+ */
331
+ type CreateEntityProps<T> = BaseEntityProps & {
332
+ props: T;
333
+ };
334
+ /**
335
+ * Abstract base class for domain entities in a Domain-Driven Design (DDD) context.
336
+ * Provides common functionality for entity identification, equality comparison,
337
+ * immutability, and validation. Entities extending this class must implement
338
+ * the `validate` method to enforce domain invariants.
339
+ * @template EntityProps - The specific props type for the entity
340
+ */
341
+ declare abstract class Entity<EntityProps> {
342
+ #private;
343
+ /**
344
+ * Returns the creation timestamp.
345
+ * A new Date instance is returned to preserve immutability.
346
+ *
347
+ * @returns {Date} The creation date of the entity.
348
+ */
349
+ get createdAt(): Date;
350
+ /**
351
+ * Gets the entity's unique identifier.
352
+ * @returns The entity's ID
353
+ */
354
+ get id(): AggregateId;
355
+ get metadata(): Readonly<{
356
+ createdAt: string;
357
+ id: string;
358
+ }>;
359
+ /**
360
+ * Returns an immutable shallow copy of the entity's properties.
361
+ *
362
+ * @returns {Readonly<EntityProps>} The entity domain properties.
363
+ */
364
+ get props(): Readonly<EntityProps>;
365
+ /**
366
+ * Constructs an entity with the provided properties and timestamps.
367
+ * Ensures immutability by cloning props and validates the entity.
368
+ * @param params - Entity creation parameters
369
+ * @throws EntityValidationError if the ID is empty or validation fails
370
+ */
371
+ protected constructor({ createdAt, props, id, }: CreateEntityProps<EntityProps>);
372
+ /**
373
+ * Checks if the provided value is an instance of Entity.
374
+ * @param entity - The value to check
375
+ * @returns True if the value is an Entity instance
376
+ */
377
+ static isEntity(entity: unknown): entity is EntityBaseInterface;
378
+ /**
379
+ * Compares this entity with another to determine if they are the same.
380
+ * Equality is based on the entity ID.
381
+ * @param other - The entity to compare with
382
+ * @returns True if the entities have the same ID
383
+ */
384
+ equals(other?: Entity<EntityProps>): boolean;
385
+ /**
386
+ * Returns a frozen copy of the entity's properties, including base properties.
387
+ * Ensures immutability by returning a new object.
388
+ * @returns A frozen copy of the entity's properties
389
+ */
390
+ getPropsCopy(): Readonly<EntityProps>;
391
+ /**
392
+ * Determines if the entity is transient, i.e., it has not been persisted yet.
393
+ * By convention, an entity is considered transient if it lacks a valid identifier.
394
+ * This can be useful when performing logic that depends on persistence state,
395
+ * such as conditional inserts or validations that only apply to new entities.
396
+ *
397
+ * @returns True if the entity is transient (not persisted), otherwise false.
398
+ */
399
+ isPersisted(): boolean;
400
+ toJSON(): Record<string, unknown>;
401
+ toObject(): Readonly<UnwrapValueObject<EntityProps> & {
402
+ createdAt: string;
403
+ id: string;
404
+ }>;
405
+ /**
406
+ * Validates the entity's state to enforce domain invariants.
407
+ * Must be implemented by subclasses to define specific validation rules.
408
+ * @throws EntityValidationError if validation fails
409
+ */
410
+ abstract validate(): void;
411
+ }
412
+
413
+ type Primitive = boolean | number | string | null;
414
+ type Serializable = Primitive | Serializable[] | {
415
+ [key: string]: Serializable;
416
+ };
417
+ type DomainEventPayload = Record<string, Serializable>;
418
+ type DomainEventProps<T> = {
419
+ id: string;
420
+ aggregateId: string;
421
+ schemaVersion: number;
422
+ occurredAt: number;
423
+ payload: T;
424
+ };
425
+ declare abstract class DomainEvent<T extends DomainEventPayload = DomainEventPayload> {
426
+ abstract readonly eventName: string;
427
+ readonly id: string;
428
+ readonly aggregateId: string;
429
+ readonly schemaVersion: number;
430
+ readonly occurredAt: number;
431
+ readonly payload: Readonly<T>;
432
+ protected constructor(props: DomainEventProps<T>);
433
+ toPrimitives(): {
434
+ schemaVersion: number;
435
+ aggregateId: string;
436
+ occurredAt: number;
437
+ eventName: string;
438
+ payload: Readonly<T>;
439
+ id: string;
440
+ };
441
+ }
442
+
443
+ /**
444
+ * Interface for AggregateRoot to ensure type safety and extensibility.
445
+ */
446
+ interface AggregateRootInterface {
447
+ readonly id: AggregateId;
448
+ readonly domainEvents: readonly DomainEvent[];
449
+ /**
450
+ * Validates the aggregate's invariants.
451
+ * @throws {EntityValidationError} If validation fails.
452
+ */
453
+ validate: () => void;
454
+ /**
455
+ * Adds a domain event to the aggregate.
456
+ * @param event The domain event to add.
457
+ */
458
+ addEvent: (event: DomainEvent) => void;
459
+ /**
460
+ * Retrieves and clears all domain events recorded by this aggregate.
461
+ *
462
+ * Domain events represent facts that occurred as a result of state changes
463
+ * within the aggregate. This method transfers ownership of those events
464
+ * to the application layer for further processing (e.g. publishing).
465
+ *
466
+ * Calling this method has the side effect of clearing the aggregate's
467
+ * internal event collection to prevent duplicate handling.
468
+ *
469
+ * This method is intended to be invoked by application services
470
+ * after the aggregate has been successfully persisted.
471
+ *
472
+ * @returns A read-only list of domain events raised by this aggregate.
473
+ */
474
+ pullDomainEvents: () => readonly DomainEvent[];
475
+ }
476
+ /**
477
+ * Base class for aggregate roots in DDD, encapsulating domain events and validation.
478
+ * @template EntityProps The type of the entity's properties.
479
+ */
480
+ declare abstract class AggregateRoot<EntityProps> extends Entity<EntityProps> implements AggregateRootInterface {
481
+ /**
482
+ * Gets a read-only copy of the domain events.
483
+ */
484
+ get domainEvents(): readonly DomainEvent[];
485
+ /**
486
+ * Internal list of domain events.
487
+ */
488
+ private readonly _domainEvents;
489
+ /**
490
+ * Adds a domain event to the aggregate after validating invariants.
491
+ * @param domainEvent The domain event to add.
492
+ * @throws {EntityValidationError} If invariants are not met.
493
+ */
494
+ addEvent(domainEvent: DomainEvent): void;
495
+ pullDomainEvents(): readonly DomainEvent[];
496
+ /**
497
+ * Validates the entity's invariants.
498
+ * @throws {EntityValidationError} If validation fails.
499
+ */
500
+ abstract validate(): void;
501
+ }
502
+
503
+ interface DomainErrorMetadata {
504
+ cause?: {
505
+ name: string;
506
+ message: string;
507
+ stack?: string;
508
+ };
509
+ [key: string]: unknown;
510
+ }
511
+ /**
512
+ * Base class for all Domain Errors in the application.
513
+ *
514
+ * This class ensures:
515
+ * 1. Serializable and structured for logs or API responses.
516
+ * 2. Identifiable via stable error codes (not just class names).
517
+ * 3. Contextual with optional structured metadata.
518
+ * 4. Supports error chaining (cause) and stack trace preservation.
519
+ *
520
+ * @example
521
+ * export class InsufficientFundsError extends DomainError {
522
+ * constructor(accountId: string, currentBalance: number) {
523
+ * super(
524
+ * `Account ${accountId} has insufficient funds.`,
525
+ * 'INSUFFICIENT_FUNDS',
526
+ * { accountId, currentBalance }
527
+ * );
528
+ * }
529
+ * }
530
+ */
531
+ declare abstract class DomainError extends Error {
532
+ /** Stable, machine-readable error code */
533
+ readonly code: string;
534
+ /** Structured, immutable domain metadata */
535
+ readonly metadata: Readonly<DomainErrorMetadata>;
536
+ /**
537
+ * @param message - Human-readable error message
538
+ * @param code - Stable error code
539
+ * @param metadata - Domain-specific structured data; optional `cause` can be included
540
+ */
541
+ protected constructor(message: string, code: string, metadata?: DomainErrorMetadata);
542
+ }
543
+
544
+ /**
545
+ * Custom error class for entity validation failures.
546
+ */
547
+ declare class EntityValidationError extends DomainError {
548
+ constructor(message: string, cause?: Error);
549
+ }
550
+
551
+ declare class InvalidValueObjectError extends DomainError {
552
+ constructor(message: string);
553
+ }
554
+
555
+ export { AggregateId, AggregateRoot, type AggregateRootInterface, ApplicationError, type ApplicationServicePort, type BaseEntityProps, type CreateEntityProps, DomainError, type DomainErrorMetadata, DomainEvent, type DomainEventPayload, Entity, type EntityBaseInterface, EntityValidationError, HttpStatus, type HttpStatusCode, HttpStatusMessage, InvalidValueObjectError, type UnwrapValueObject, ValueObject, deepFreeze, ensureObject, unwrapValueObject };