@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.
- package/CHANGELOG.md +21 -103
- package/LICENSE +20 -20
- package/dist/base-entity.d.ts +1 -1
- package/dist/base-entity.d.ts.map +1 -1
- package/dist/base-entity.js +15 -19
- package/dist/base-entity.js.map +1 -1
- package/dist/constants.js +1 -4
- package/dist/constants.js.map +1 -1
- package/dist/criteria.d.ts +5 -11
- package/dist/criteria.d.ts.map +1 -1
- package/dist/criteria.js +5 -8
- package/dist/criteria.js.map +1 -1
- package/dist/deep-proxy.d.ts.map +1 -1
- package/dist/deep-proxy.js +19 -9
- package/dist/deep-proxy.js.map +1 -1
- package/dist/domain-event-bus.d.ts +5 -6
- package/dist/domain-event-bus.d.ts.map +1 -1
- package/dist/domain-event-bus.js +4 -17
- package/dist/domain-event-bus.js.map +1 -1
- package/dist/domain-event.d.ts +1 -31
- package/dist/domain-event.d.ts.map +1 -1
- package/dist/domain-event.js +4 -7
- package/dist/domain-event.js.map +1 -1
- package/dist/entity.d.ts +2 -2
- package/dist/entity.js +3 -8
- package/dist/entity.js.map +1 -1
- package/dist/exceptions.d.ts +251 -0
- package/dist/exceptions.d.ts.map +1 -0
- package/dist/exceptions.js +321 -0
- package/dist/exceptions.js.map +1 -0
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +14 -7
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -39
- package/dist/index.js.map +1 -1
- package/dist/mapper.js +1 -5
- package/dist/mapper.js.map +1 -1
- package/dist/paginated-result.d.ts.map +1 -1
- package/dist/paginated-result.js +15 -8
- package/dist/paginated-result.js.map +1 -1
- package/dist/repository/base-repository.js +4 -11
- package/dist/repository/base-repository.js.map +1 -1
- package/dist/repository/index.d.ts +1 -2
- package/dist/repository/index.d.ts.map +1 -1
- package/dist/repository/index.js +3 -26
- package/dist/repository/index.js.map +1 -1
- package/dist/repository/unit-of-work.d.ts +0 -11
- package/dist/repository/unit-of-work.d.ts.map +1 -1
- package/dist/repository/unit-of-work.js +2 -43
- package/dist/repository/unit-of-work.js.map +1 -1
- package/dist/types/criteria.d.ts +4 -0
- package/dist/types/criteria.d.ts.map +1 -1
- package/dist/types/criteria.js +1 -4
- package/dist/types/criteria.js.map +1 -1
- package/dist/types/domain-event.d.ts +32 -0
- package/dist/types/domain-event.d.ts.map +1 -0
- package/dist/types/domain-event.js +2 -0
- package/dist/types/domain-event.js.map +1 -0
- package/dist/types/domain.d.ts +2 -2
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/types/domain.js +1 -2
- package/dist/types/history-tracker.d.ts +1 -1
- package/dist/types/history-tracker.d.ts.map +1 -1
- package/dist/types/history-tracker.js +1 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -22
- package/dist/types/index.js.map +1 -1
- package/dist/types/standard-schema.js +1 -2
- package/dist/types/unit-of-work.js +1 -2
- package/dist/types/utils.js +1 -2
- package/dist/validation-error.js +3 -9
- package/dist/validation-error.js.map +1 -1
- package/dist/value-object.d.ts +1 -1
- package/dist/value-object.d.ts.map +1 -1
- package/dist/value-object.js +7 -14
- package/dist/value-object.js.map +1 -1
- package/eslint.config.js +3 -3
- package/jest.config.js +1 -1
- package/package.json +14 -20
- package/src/base-entity.ts +3 -3
- package/src/criteria.ts +9 -9
- package/src/deep-proxy.ts +50 -38
- package/src/domain-event-bus.ts +152 -166
- package/src/domain-event.ts +53 -90
- package/src/entity.ts +16 -16
- package/src/exceptions.ts +435 -0
- package/src/id.ts +107 -94
- package/src/index.ts +25 -6
- package/src/paginated-result.ts +14 -1
- package/src/repository/index.ts +1 -6
- package/src/repository/unit-of-work.ts +1 -44
- package/src/types/criteria.ts +5 -0
- package/src/types/domain-event.ts +38 -0
- package/src/types/domain.ts +2 -3
- package/src/types/history-tracker.ts +1 -1
- package/src/types/index.ts +1 -0
- package/src/validation-error.ts +97 -97
- package/src/value-object.ts +3 -6
- package/tests/criteria.test.ts +8 -0
- package/tests/domain-events.test.ts +431 -445
- package/tests/entity-validation.test.ts +1 -1
- package/tests/entity.test.ts +33 -33
- package/tests/repository.test.ts +4 -2
- package/tests/utils.ts +254 -151
- package/tests/value-object-validation.test.ts +0 -9
- package/tsconfig.json +2 -24
- package/.github/workflows/ci.yml +0 -40
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.vscode/settings.json +0 -3
- package/commitlint.config.js +0 -23
- package/dist/repository/in-memory-repository.d.ts +0 -50
- package/dist/repository/in-memory-repository.d.ts.map +0 -1
- package/dist/repository/in-memory-repository.js +0 -97
- package/dist/repository/in-memory-repository.js.map +0 -1
- package/src/repository/in-memory-repository.ts +0 -116
package/src/id.ts
CHANGED
|
@@ -1,94 +1,107 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
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";
|
package/src/paginated-result.ts
CHANGED
|
@@ -70,7 +70,20 @@ export class PaginatedResult<T> {
|
|
|
70
70
|
result = result.filter((item) => applyFilter(item, filter));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
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()) {
|
package/src/repository/index.ts
CHANGED
|
@@ -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
|
-
}
|
package/src/types/criteria.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/types/domain.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
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)
|
package/src/types/index.ts
CHANGED
package/src/validation-error.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/value-object.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ============================================================================
|
|
4
4
|
|
|
5
5
|
import { ValidationError } from "./validation-error";
|
|
6
|
-
import { IDomainEvent } from "
|
|
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
|
|
84
|
+
throw new DomainError(
|
|
88
85
|
"Async validation not supported in constructor. Use sync validation schema."
|
|
89
86
|
);
|
|
90
87
|
}
|