@woltz/rich-domain 1.0.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +21 -103
  2. package/LICENSE +20 -20
  3. package/dist/base-entity.d.ts +1 -1
  4. package/dist/base-entity.d.ts.map +1 -1
  5. package/dist/base-entity.js +15 -19
  6. package/dist/base-entity.js.map +1 -1
  7. package/dist/constants.js +1 -4
  8. package/dist/constants.js.map +1 -1
  9. package/dist/criteria.d.ts +5 -11
  10. package/dist/criteria.d.ts.map +1 -1
  11. package/dist/criteria.js +5 -8
  12. package/dist/criteria.js.map +1 -1
  13. package/dist/deep-proxy.d.ts.map +1 -1
  14. package/dist/deep-proxy.js +19 -9
  15. package/dist/deep-proxy.js.map +1 -1
  16. package/dist/domain-event-bus.d.ts +5 -6
  17. package/dist/domain-event-bus.d.ts.map +1 -1
  18. package/dist/domain-event-bus.js +4 -17
  19. package/dist/domain-event-bus.js.map +1 -1
  20. package/dist/domain-event.d.ts +1 -31
  21. package/dist/domain-event.d.ts.map +1 -1
  22. package/dist/domain-event.js +4 -7
  23. package/dist/domain-event.js.map +1 -1
  24. package/dist/entity.d.ts +2 -2
  25. package/dist/entity.js +3 -8
  26. package/dist/entity.js.map +1 -1
  27. package/dist/exceptions.d.ts +251 -0
  28. package/dist/exceptions.d.ts.map +1 -0
  29. package/dist/exceptions.js +321 -0
  30. package/dist/exceptions.js.map +1 -0
  31. package/dist/id.d.ts.map +1 -1
  32. package/dist/id.js +14 -7
  33. package/dist/id.js.map +1 -1
  34. package/dist/index.d.ts +2 -4
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +11 -39
  37. package/dist/index.js.map +1 -1
  38. package/dist/mapper.js +1 -5
  39. package/dist/mapper.js.map +1 -1
  40. package/dist/paginated-result.d.ts.map +1 -1
  41. package/dist/paginated-result.js +15 -8
  42. package/dist/paginated-result.js.map +1 -1
  43. package/dist/repository/base-repository.js +4 -11
  44. package/dist/repository/base-repository.js.map +1 -1
  45. package/dist/repository/index.d.ts +1 -2
  46. package/dist/repository/index.d.ts.map +1 -1
  47. package/dist/repository/index.js +3 -26
  48. package/dist/repository/index.js.map +1 -1
  49. package/dist/repository/unit-of-work.d.ts +0 -11
  50. package/dist/repository/unit-of-work.d.ts.map +1 -1
  51. package/dist/repository/unit-of-work.js +2 -43
  52. package/dist/repository/unit-of-work.js.map +1 -1
  53. package/dist/types/criteria.d.ts +4 -0
  54. package/dist/types/criteria.d.ts.map +1 -1
  55. package/dist/types/criteria.js +1 -4
  56. package/dist/types/criteria.js.map +1 -1
  57. package/dist/types/domain-event.d.ts +32 -0
  58. package/dist/types/domain-event.d.ts.map +1 -0
  59. package/dist/types/domain-event.js +2 -0
  60. package/dist/types/domain-event.js.map +1 -0
  61. package/dist/types/domain.d.ts +2 -2
  62. package/dist/types/domain.d.ts.map +1 -1
  63. package/dist/types/domain.js +1 -2
  64. package/dist/types/history-tracker.d.ts +1 -1
  65. package/dist/types/history-tracker.d.ts.map +1 -1
  66. package/dist/types/history-tracker.js +1 -2
  67. package/dist/types/index.d.ts +1 -0
  68. package/dist/types/index.d.ts.map +1 -1
  69. package/dist/types/index.js +7 -22
  70. package/dist/types/index.js.map +1 -1
  71. package/dist/types/standard-schema.js +1 -2
  72. package/dist/types/unit-of-work.js +1 -2
  73. package/dist/types/utils.js +1 -2
  74. package/dist/validation-error.js +3 -9
  75. package/dist/validation-error.js.map +1 -1
  76. package/dist/value-object.d.ts +1 -1
  77. package/dist/value-object.d.ts.map +1 -1
  78. package/dist/value-object.js +7 -14
  79. package/dist/value-object.js.map +1 -1
  80. package/eslint.config.js +3 -3
  81. package/jest.config.js +1 -1
  82. package/package.json +14 -20
  83. package/src/base-entity.ts +3 -3
  84. package/src/criteria.ts +9 -9
  85. package/src/deep-proxy.ts +50 -38
  86. package/src/domain-event-bus.ts +152 -166
  87. package/src/domain-event.ts +53 -90
  88. package/src/entity.ts +16 -16
  89. package/src/exceptions.ts +435 -0
  90. package/src/id.ts +107 -94
  91. package/src/index.ts +25 -6
  92. package/src/paginated-result.ts +14 -1
  93. package/src/repository/index.ts +1 -6
  94. package/src/repository/unit-of-work.ts +1 -44
  95. package/src/types/criteria.ts +5 -0
  96. package/src/types/domain-event.ts +38 -0
  97. package/src/types/domain.ts +2 -3
  98. package/src/types/history-tracker.ts +1 -1
  99. package/src/types/index.ts +1 -0
  100. package/src/validation-error.ts +97 -97
  101. package/src/value-object.ts +3 -6
  102. package/tests/criteria.test.ts +8 -0
  103. package/tests/domain-events.test.ts +431 -445
  104. package/tests/entity-validation.test.ts +1 -1
  105. package/tests/entity.test.ts +33 -33
  106. package/tests/repository.test.ts +4 -2
  107. package/tests/utils.ts +254 -151
  108. package/tests/value-object-validation.test.ts +0 -9
  109. package/tsconfig.json +2 -24
  110. package/.github/workflows/ci.yml +0 -40
  111. package/.husky/commit-msg +0 -1
  112. package/.husky/pre-commit +0 -1
  113. package/.vscode/settings.json +0 -3
  114. package/commitlint.config.js +0 -23
  115. package/dist/repository/in-memory-repository.d.ts +0 -50
  116. package/dist/repository/in-memory-repository.d.ts.map +0 -1
  117. package/dist/repository/in-memory-repository.js +0 -97
  118. package/dist/repository/in-memory-repository.js.map +0 -1
  119. package/src/repository/in-memory-repository.ts +0 -116
package/src/id.ts CHANGED
@@ -1,94 +1,107 @@
1
- import { randomUUID } from "crypto";
2
- // ============================================================================
3
- // Id Class - Smart Identity Management
4
- // ============================================================================
5
-
6
- export class Id {
7
- private readonly _value: string;
8
- private readonly _isNew: boolean;
9
-
10
- /**
11
- * Create a new Id
12
- * @param value - Optional existing ID value. If not provided, generates a new UUID.
13
- *
14
- * @example
15
- * // New entity (generates UUID)
16
- * const newId = new Id();
17
- * newuser.isNew() // true
18
- *
19
- * // Existing entity (uses provided ID)
20
- * const existingId = new Id("550e8400-e29b-41d4-a716-446655440000");
21
- * existinguser.isNew() // false
22
- */
23
- constructor(value?: string) {
24
- if (value !== undefined) {
25
- // ID was provided - this is an existing entity
26
- this._value = value;
27
- this._isNew = false;
28
- } else {
29
- // No ID provided - generate new one, this is a new entity
30
- this._value = this.generateUUID();
31
- this._isNew = true;
32
- }
33
- }
34
-
35
- /**
36
- * Get the string value of the ID
37
- */
38
- get value(): string {
39
- return this._value;
40
- }
41
-
42
- /**
43
- * Check if this ID represents a new entity
44
- */
45
- public isNew(): boolean {
46
- return this._isNew;
47
- }
48
-
49
- /**
50
- * Convert to string (for JSON serialization and comparisons)
51
- */
52
- toString(): string {
53
- return this._value;
54
- }
55
-
56
- /**
57
- * Convert to JSON (returns the string value)
58
- */
59
- toJSON(): string {
60
- return this._value;
61
- }
62
-
63
- /**
64
- * Check equality with another Id or string
65
- */
66
- equals(other: Id | string): boolean {
67
- if (other instanceof Id) {
68
- return this._value === other._value;
69
- }
70
- return this._value === other;
71
- }
72
-
73
- /**
74
- * Generate a UUID v4
75
- */
76
- private generateUUID(): string {
77
- // Simple UUID v4 implementation
78
- return randomUUID();
79
- }
80
-
81
- /**
82
- * Create a new Id (convenience static method)
83
- */
84
- static create(): Id {
85
- return new Id();
86
- }
87
-
88
- /**
89
- * Create an Id from an existing value
90
- */
91
- static from(value: string): Id {
92
- return new Id(value);
93
- }
94
- }
1
+ // ============================================================================
2
+ // Id Class - Smart Identity Management
3
+ // ============================================================================
4
+
5
+ function randomUUID(): string {
6
+ // If we are in the browser, use the browser's crypto API
7
+ // @ts-expect-error - window.crypto is not defined in the browser
8
+ if (typeof window !== "undefined" && window.crypto) {
9
+ // @ts-expect-error - window.crypto is not defined in the browser
10
+ return window.crypto.randomUUID();
11
+ }
12
+ // If we are in the server, use the crypto library
13
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
14
+ const crypto = require("crypto");
15
+
16
+ return crypto.randomUUID();
17
+ }
18
+
19
+ export class Id {
20
+ private readonly _value: string;
21
+ private readonly _isNew: boolean;
22
+
23
+ /**
24
+ * Create a new Id
25
+ * @param value - Optional existing ID value. If not provided, generates a new UUID.
26
+ *
27
+ * @example
28
+ * // New entity (generates UUID)
29
+ * const newId = new Id();
30
+ * newuser.isNew() // true
31
+ *
32
+ * // Existing entity (uses provided ID)
33
+ * const existingId = new Id("550e8400-e29b-41d4-a716-446655440000");
34
+ * existinguser.isNew() // false
35
+ */
36
+ constructor(value?: string) {
37
+ if (value !== undefined) {
38
+ // ID was provided - this is an existing entity
39
+ this._value = value;
40
+ this._isNew = false;
41
+ } else {
42
+ // No ID provided - generate new one, this is a new entity
43
+ this._value = this.generateUUID();
44
+ this._isNew = true;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Get the string value of the ID
50
+ */
51
+ get value(): string {
52
+ return this._value;
53
+ }
54
+
55
+ /**
56
+ * Check if this ID represents a new entity
57
+ */
58
+ public isNew(): boolean {
59
+ return this._isNew;
60
+ }
61
+
62
+ /**
63
+ * Convert to string (for JSON serialization and comparisons)
64
+ */
65
+ toString(): string {
66
+ return this._value;
67
+ }
68
+
69
+ /**
70
+ * Convert to JSON (returns the string value)
71
+ */
72
+ toJSON(): string {
73
+ return this._value;
74
+ }
75
+
76
+ /**
77
+ * Check equality with another Id or string
78
+ */
79
+ equals(other: Id | string): boolean {
80
+ if (other instanceof Id) {
81
+ return this._value === other._value;
82
+ }
83
+ return this._value === other;
84
+ }
85
+
86
+ /**
87
+ * Generate a UUID v4
88
+ */
89
+ private generateUUID(): string {
90
+ // Simple UUID v4 implementation
91
+ return randomUUID();
92
+ }
93
+
94
+ /**
95
+ * Create a new Id (convenience static method)
96
+ */
97
+ static create(): Id {
98
+ return new Id();
99
+ }
100
+
101
+ /**
102
+ * Create an Id from an existing value
103
+ */
104
+ static from(value: string): Id {
105
+ return new Id(value);
106
+ }
107
+ }
package/src/index.ts CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  // Core Classes
6
6
  export { Id } from "./id";
7
- export { BaseEntity } from "./base-entity";
8
7
  export { Entity, Aggregate } from "./entity";
9
8
  export { ValueObject } from "./value-object";
10
9
  export { Mapper } from "./mapper";
@@ -13,8 +12,8 @@ export * from "./validation-error";
13
12
 
14
13
  // Domain Events
15
14
  export * from "./domain-event";
16
-
17
15
  export * from "./domain-event-bus";
16
+ export * from "./exceptions";
18
17
 
19
18
  // Criteria
20
19
  export * from "./criteria";
@@ -24,9 +23,29 @@ export * from "./paginated-result";
24
23
  export * from "./repository";
25
24
 
26
25
  // Types
27
- export * from "./types";
28
-
29
- export * from "./constants";
26
+ export {
27
+ DomainEventHandler,
28
+ EntityHooks,
29
+ Filter,
30
+ EntityValidation,
31
+ IDomainEvent,
32
+ VOValidation,
33
+ VOHooks,
34
+ ValidationConfig,
35
+ Primitive,
36
+ TransactionContext,
37
+ PaginationMeta,
38
+ Pagination,
39
+ OrderDirection,
40
+ Order,
41
+ IUnitOfWork,
42
+ IDomainEventHandler,
43
+ EntityId,
44
+ FieldPath,
45
+ FilterOperator,
46
+ Search,
47
+ FilterValueFor,
48
+ PathValue,
49
+ } from "./types";
30
50
 
31
51
  // Internal (for advanced usage)
32
- export { DeepProxy } from "./deep-proxy";
@@ -70,7 +70,20 @@ export class PaginatedResult<T> {
70
70
  result = result.filter((item) => applyFilter(item, filter));
71
71
  }
72
72
 
73
- const total = result.length;
73
+ let total = result.length;
74
+
75
+ const search = criteria.getSearch();
76
+ if (search) {
77
+ result = result.filter((item) => {
78
+ return search.fields.some((field) => {
79
+ return String(getNestedValue(item, field))
80
+ .toLowerCase()
81
+ .includes(search.value.toLowerCase());
82
+ });
83
+ });
84
+
85
+ total = result.length;
86
+ }
74
87
 
75
88
  // Apply ordering
76
89
  for (const order of criteria.getOrders().reverse()) {
@@ -7,11 +7,6 @@ export { Mapper } from "../mapper";
7
7
 
8
8
  // Base implementations
9
9
  export * from "./base-repository";
10
- export { InMemoryRepository } from "./in-memory-repository";
11
10
 
12
11
  // Unit of Work
13
- export {
14
- UnitOfWork,
15
- BaseTransactionContext,
16
- InMemoryUnitOfWork,
17
- } from "./unit-of-work";
12
+ export { UnitOfWork, BaseTransactionContext } from "./unit-of-work";
@@ -66,9 +66,7 @@ export abstract class UnitOfWork implements IUnitOfWork {
66
66
  /**
67
67
  * Get repository instance (cached per transaction)
68
68
  */
69
- getRepository<TRepo>(
70
- RepositoryClass: new (...args: any[]) => TRepo
71
- ): TRepo {
69
+ getRepository<TRepo>(RepositoryClass: new (...args: any[]) => TRepo): TRepo {
72
70
  const key = RepositoryClass.name;
73
71
 
74
72
  if (this.repositoryCache.has(key)) {
@@ -105,44 +103,3 @@ export abstract class BaseTransactionContext implements TransactionContext {
105
103
  this._isActive = false;
106
104
  }
107
105
  }
108
-
109
- /**
110
- * In-Memory Unit of Work (for testing)
111
- */
112
- export class InMemoryUnitOfWork extends UnitOfWork {
113
- private committed = false;
114
- private rolledBack = false;
115
-
116
- async begin(): Promise<TransactionContext> {
117
- this.currentContext = new InMemoryTransactionContext();
118
- this.committed = false;
119
- this.rolledBack = false;
120
- return this.currentContext;
121
- }
122
-
123
- protected createRepository<TRepo>(
124
- RepositoryClass: new (...args: any[]) => TRepo
125
- ): TRepo {
126
- // For in-memory, just create a new instance
127
- // In real implementation, pass transaction client
128
- return new RepositoryClass();
129
- }
130
-
131
- isCommitted(): boolean {
132
- return this.committed;
133
- }
134
-
135
- isRolledBack(): boolean {
136
- return this.rolledBack;
137
- }
138
- }
139
-
140
- class InMemoryTransactionContext extends BaseTransactionContext {
141
- async commit(): Promise<void> {
142
- this.markInactive();
143
- }
144
-
145
- async rollback(): Promise<void> {
146
- this.markInactive();
147
- }
148
- }
@@ -61,6 +61,11 @@ export interface Pagination {
61
61
  offset: number;
62
62
  }
63
63
 
64
+ export interface Search<T> {
65
+ fields: FieldPath<T>[];
66
+ value: string;
67
+ }
68
+
64
69
  export interface PaginationMeta {
65
70
  page: number;
66
71
  limit: number;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Interface for all domain events
3
+ */
4
+ export interface IDomainEvent {
5
+ /**
6
+ * Unique identifier for this event occurrence
7
+ */
8
+ readonly eventId: string;
9
+
10
+ /**
11
+ * When the event occurred
12
+ */
13
+ readonly occurredOn: Date;
14
+
15
+ /**
16
+ * Name/type of the event (e.g., "UserCreated", "OrderPlaced")
17
+ */
18
+ readonly eventName: string;
19
+
20
+ /**
21
+ * ID of the aggregate that raised this event
22
+ */
23
+ readonly aggregateId: string;
24
+ }
25
+
26
+ /**
27
+ * Event handler function type
28
+ */
29
+ export type DomainEventHandler<T extends IDomainEvent = IDomainEvent> = (
30
+ event: T
31
+ ) => void | Promise<void>;
32
+
33
+ /**
34
+ * Event handler class type
35
+ */
36
+ export interface IDomainEventHandler<T extends IDomainEvent = IDomainEvent> {
37
+ handle(event: T): void | Promise<void>;
38
+ }
@@ -1,5 +1,6 @@
1
- import { StandardSchema, ValidationConfig } from "..";
1
+ import { ValidationConfig } from "..";
2
2
  import { Id } from "../id";
3
+ import { StandardSchema } from "./standard-schema";
3
4
 
4
5
  export type EntityId = string | number;
5
6
 
@@ -15,12 +16,10 @@ interface DomainValidation<T> {
15
16
  export type EntityValidation<T> = DomainValidation<T>;
16
17
  export type VOValidation<T> = DomainValidation<T>;
17
18
 
18
-
19
19
  export interface VOHooks<T, E> {
20
20
  onBeforeUpdate?: (entity: E, snapshot: T) => boolean;
21
21
  onCreate?: (entity: E) => void;
22
22
  rules?: (entity: E) => void;
23
- defaultValues?: Partial<T>;
24
23
  }
25
24
 
26
25
  // Specialized hooks for entities (with BaseProps)
@@ -1,4 +1,4 @@
1
- import { BaseProps } from "..";
1
+ import { BaseProps } from "./domain";
2
2
  import { IsArray, NonUndefined, UnwrapArray } from "./utils";
3
3
 
4
4
  export interface ChangeEvent<T> {
@@ -4,3 +4,4 @@ export * from "./history-tracker";
4
4
  export * from "./standard-schema";
5
5
  export * from "./utils";
6
6
  export * from "./unit-of-work";
7
+ export * from "./domain-event";
@@ -1,97 +1,97 @@
1
- // ============================================================================
2
- // Validation Error - Domain Validation Errors
3
- // ============================================================================
4
-
5
- export interface ValidationIssue {
6
- path: string[];
7
- message: string;
8
- }
9
-
10
- export class ValidationError extends Error {
11
- public readonly issues: ValidationIssue[];
12
- public readonly __isValidationError = true; // Brand for identification
13
-
14
- constructor(issues: ValidationIssue[], message?: string) {
15
- const errorMessage =
16
- message || `Validation failed: ${issues.map(i => i.message).join(', ')}`;
17
- super(errorMessage);
18
- this.name = 'ValidationError';
19
- this.issues = issues;
20
-
21
- // Maintain proper stack trace
22
- if (Error.captureStackTrace) {
23
- Error.captureStackTrace(this, ValidationError);
24
- }
25
- }
26
-
27
- /**
28
- * Check if an error is a ValidationError (works across module boundaries)
29
- */
30
- static isValidationError(error: unknown): error is ValidationError {
31
- if (error instanceof ValidationError) {
32
- return true;
33
- }
34
- // Check by duck typing for cross-module compatibility
35
- return (
36
- error instanceof Error &&
37
- error.name === 'ValidationError' &&
38
- 'issues' in error &&
39
- Array.isArray((error as any).issues)
40
- );
41
- }
42
-
43
- /**
44
- * Get all error messages as a simple array
45
- */
46
- getMessages(): string[] {
47
- return this.issues.map(i => i.message);
48
- }
49
-
50
- /**
51
- * Get errors for a specific field path
52
- */
53
- getErrorsForPath(path: string): ValidationIssue[] {
54
- return this.issues.filter(i => i.path.join('.') === path);
55
- }
56
-
57
- /**
58
- * Check if a specific path has errors
59
- */
60
- hasErrorsForPath(path: string): boolean {
61
- return this.getErrorsForPath(path).length > 0;
62
- }
63
-
64
- /**
65
- * Convert to a plain object for serialization
66
- */
67
- toJSON(): { name: string; message: string; issues: ValidationIssue[] } {
68
- return {
69
- name: this.name,
70
- message: this.message,
71
- issues: this.issues,
72
- };
73
- }
74
- }
75
-
76
- /**
77
- * Helper to create a single validation issue
78
- */
79
- export function createValidationIssue(
80
- path: string | string[],
81
- message: string
82
- ): ValidationIssue {
83
- return {
84
- path: Array.isArray(path) ? path : path.split('.'),
85
- message,
86
- };
87
- }
88
-
89
- /**
90
- * Helper to throw a validation error with a single issue
91
- */
92
- export function throwValidationError(
93
- path: string | string[],
94
- message: string
95
- ): never {
96
- throw new ValidationError([createValidationIssue(path, message)]);
97
- }
1
+ // ============================================================================
2
+ // Validation Error - Domain Validation Errors
3
+ // ============================================================================
4
+
5
+ export interface ValidationIssue {
6
+ path: string[];
7
+ message: string;
8
+ }
9
+
10
+ export class ValidationError extends Error {
11
+ public readonly issues: ValidationIssue[];
12
+ public readonly __isValidationError = true; // Brand for identification
13
+
14
+ constructor(issues: ValidationIssue[], message?: string) {
15
+ const errorMessage =
16
+ message || `Validation failed: ${issues.map(i => i.message).join(', ')}`;
17
+ super(errorMessage);
18
+ this.name = 'ValidationError';
19
+ this.issues = issues;
20
+
21
+ // Maintain proper stack trace
22
+ if (Error.captureStackTrace) {
23
+ Error.captureStackTrace(this, ValidationError);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Check if an error is a ValidationError (works across module boundaries)
29
+ */
30
+ static isValidationError(error: unknown): error is ValidationError {
31
+ if (error instanceof ValidationError) {
32
+ return true;
33
+ }
34
+ // Check by duck typing for cross-module compatibility
35
+ return (
36
+ error instanceof Error &&
37
+ error.name === 'ValidationError' &&
38
+ 'issues' in error &&
39
+ Array.isArray((error as any).issues)
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Get all error messages as a simple array
45
+ */
46
+ getMessages(): string[] {
47
+ return this.issues.map(i => i.message);
48
+ }
49
+
50
+ /**
51
+ * Get errors for a specific field path
52
+ */
53
+ getErrorsForPath(path: string): ValidationIssue[] {
54
+ return this.issues.filter(i => i.path.join('.') === path);
55
+ }
56
+
57
+ /**
58
+ * Check if a specific path has errors
59
+ */
60
+ hasErrorsForPath(path: string): boolean {
61
+ return this.getErrorsForPath(path).length > 0;
62
+ }
63
+
64
+ /**
65
+ * Convert to a plain object for serialization
66
+ */
67
+ toJSON(): { name: string; message: string; issues: ValidationIssue[] } {
68
+ return {
69
+ name: this.name,
70
+ message: this.message,
71
+ issues: this.issues,
72
+ };
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Helper to create a single validation issue
78
+ */
79
+ export function createValidationIssue(
80
+ path: string | string[],
81
+ message: string
82
+ ): ValidationIssue {
83
+ return {
84
+ path: Array.isArray(path) ? path : path.split('.'),
85
+ message,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Helper to throw a validation error with a single issue
91
+ */
92
+ export function throwValidationError(
93
+ path: string | string[],
94
+ message: string
95
+ ): never {
96
+ throw new ValidationError([createValidationIssue(path, message)]);
97
+ }
@@ -3,7 +3,7 @@
3
3
  // ============================================================================
4
4
 
5
5
  import { ValidationError } from "./validation-error";
6
- import { IDomainEvent } from "./domain-event";
6
+ import { IDomainEvent } from ".";
7
7
  import {
8
8
  VOHooks,
9
9
  ValidationConfig,
@@ -11,6 +11,7 @@ import {
11
11
  EntityValidation,
12
12
  } from "./types";
13
13
  import { DEFAULT_VALIDATION_CONFIG } from "./constants";
14
+ import { DomainError } from "./exceptions";
14
15
 
15
16
  // Helper to get static properties from constructor
16
17
  function getStaticProperty<T>(
@@ -50,11 +51,7 @@ export abstract class ValueObject<T> {
50
51
  ...validation?.config,
51
52
  };
52
53
 
53
- // Apply defaultValues
54
54
  let finalProps = { ...props } as T;
55
- if (hooks?.defaultValues) {
56
- finalProps = { ...hooks.defaultValues, ...props } as T;
57
- }
58
55
 
59
56
  // Validate schema on creation
60
57
  if (this.domainSchema && this.validationConfig.onCreate) {
@@ -84,7 +81,7 @@ export abstract class ValueObject<T> {
84
81
  const result = this.domainSchema["~standard"].validate(props);
85
82
 
86
83
  if (result instanceof Promise) {
87
- throw new Error(
84
+ throw new DomainError(
88
85
  "Async validation not supported in constructor. Use sync validation schema."
89
86
  );
90
87
  }