@woltz/rich-domain 0.2.2 → 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 +23 -75
- package/LICENSE +20 -20
- package/README.md +37 -20
- package/dist/base-entity.d.ts +2 -2
- package/dist/base-entity.d.ts.map +1 -1
- package/dist/base-entity.js +6 -4
- package/dist/base-entity.js.map +1 -1
- package/dist/criteria.d.ts +5 -11
- package/dist/criteria.d.ts.map +1 -1
- package/dist/criteria.js +4 -3
- package/dist/criteria.js.map +1 -1
- package/dist/deep-proxy.d.ts +3 -1
- package/dist/deep-proxy.d.ts.map +1 -1
- package/dist/deep-proxy.js +116 -29
- 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 +3 -11
- 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 +2 -1
- package/dist/domain-event.js.map +1 -1
- package/dist/entity.d.ts +2 -2
- package/dist/entity.js +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 +3 -3
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +15 -4
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -8
- package/dist/index.js.map +1 -1
- package/dist/paginated-result.d.ts.map +1 -1
- package/dist/paginated-result.js +12 -1
- package/dist/paginated-result.js.map +1 -1
- package/dist/repository/index.d.ts +2 -39
- package/dist/repository/index.d.ts.map +1 -1
- package/dist/repository/index.js +2 -39
- 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 +0 -35
- package/dist/repository/unit-of-work.js.map +1 -1
- package/dist/types/criteria.d.ts +6 -2
- package/dist/types/criteria.d.ts.map +1 -1
- package/dist/types/criteria.js +1 -1
- 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/history-tracker.d.ts +1 -1
- package/dist/types/history-tracker.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.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 +2 -5
- 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 +6 -5
- package/src/criteria.ts +11 -11
- package/src/deep-proxy.ts +447 -339
- 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 +26 -9
- package/src/paginated-result.ts +14 -1
- package/src/repository/index.ts +2 -44
- package/src/repository/unit-of-work.ts +1 -44
- package/src/types/criteria.ts +7 -2
- 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 +2 -2
- package/tests/entity.test.ts +33 -33
- package/tests/history-tracker.spec.ts +57 -17
- package/tests/id.test.ts +341 -341
- package/tests/repository.test.ts +8 -4
- package/tests/to-json.test.ts +103 -91
- package/tests/utils.ts +254 -151
- package/tests/value-object-validation.test.ts +0 -9
- package/tests/value-objects.test.ts +52 -52
- 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/filtering.d.ts +0 -107
- package/dist/filtering.d.ts.map +0 -1
- package/dist/filtering.js +0 -202
- package/dist/filtering.js.map +0 -1
- package/dist/ordering.d.ts +0 -93
- package/dist/ordering.d.ts.map +0 -1
- package/dist/ordering.js +0 -154
- package/dist/ordering.js.map +0 -1
- package/dist/pagination.d.ts +0 -218
- package/dist/pagination.d.ts.map +0 -1
- package/dist/pagination.js +0 -281
- package/dist/pagination.js.map +0 -1
- 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 -93
- package/dist/repository/in-memory-repository.js.map +0 -1
- package/dist/repository/mapper.d.ts +0 -56
- package/dist/repository/mapper.d.ts.map +0 -1
- package/dist/repository/mapper.js +0 -15
- package/dist/repository/mapper.js.map +0 -1
- package/dist/repository/types.d.ts +0 -87
- package/dist/repository/types.d.ts.map +0 -1
- package/dist/repository/types.js +0 -6
- package/dist/repository/types.js.map +0 -1
- package/dist/repository.d.ts +0 -2
- package/dist/repository.d.ts.map +0 -1
- package/dist/repository.js +0 -21
- package/dist/repository.js.map +0 -1
- package/dist/specification.d.ts +0 -102
- package/dist/specification.d.ts.map +0 -1
- package/dist/specification.js +0 -187
- package/dist/specification.js.map +0 -1
- package/dist/types/repository.d.ts +0 -43
- package/dist/types/repository.d.ts.map +0 -1
- package/dist/types/repository.js +0 -2
- package/dist/types/repository.js.map +0 -1
- package/dist/types.d.ts +0 -88
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -12
- package/dist/types.js.map +0 -1
- package/src/repository/in-memory-repository.ts +0 -116
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
|
@@ -6,49 +6,7 @@
|
|
|
6
6
|
export { Mapper } from "../mapper";
|
|
7
7
|
|
|
8
8
|
// Base implementations
|
|
9
|
-
export
|
|
10
|
-
export { InMemoryRepository } from "./in-memory-repository";
|
|
9
|
+
export * from "./base-repository";
|
|
11
10
|
|
|
12
11
|
// Unit of Work
|
|
13
|
-
export {
|
|
14
|
-
UnitOfWork,
|
|
15
|
-
BaseTransactionContext,
|
|
16
|
-
InMemoryUnitOfWork,
|
|
17
|
-
} from "./unit-of-work";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* QUICK START:
|
|
21
|
-
*
|
|
22
|
-
* 1. For Testing:
|
|
23
|
-
* ```ts
|
|
24
|
-
* import { InMemoryRepository } from 'rich-domain';
|
|
25
|
-
*
|
|
26
|
-
* const userRepo = new InMemoryRepository<User>();
|
|
27
|
-
* await userRepo.save(user);
|
|
28
|
-
* const found = await userRepo.findById(user.id);
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* 2. For Production (Prisma, TypeORM, etc):
|
|
32
|
-
* - Extend BaseRepository
|
|
33
|
-
* - Implement abstract methods
|
|
34
|
-
* - Create a Mapper
|
|
35
|
-
* - See examples/ folder for reference
|
|
36
|
-
*
|
|
37
|
-
* 3. With Criteria:
|
|
38
|
-
* ```ts
|
|
39
|
-
* const result = await userRepo.find(
|
|
40
|
-
* Criteria.create<User>()
|
|
41
|
-
* .whereEquals('status', 'active')
|
|
42
|
-
* .orderByDesc('createdAt')
|
|
43
|
-
* .paginate(1, 10)
|
|
44
|
-
* );
|
|
45
|
-
* ```
|
|
46
|
-
*
|
|
47
|
-
* 4. With Unit of Work:
|
|
48
|
-
* ```ts
|
|
49
|
-
* await uow.transaction(async (ctx) => {
|
|
50
|
-
* const userRepo = uow.getRepository(UserRepository);
|
|
51
|
-
* await userRepo.save(user);
|
|
52
|
-
* });
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Primitive } from "./utils";
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const FILTER_OPERATORS = [
|
|
4
4
|
"equals",
|
|
5
5
|
"notEquals",
|
|
6
6
|
"greaterThan",
|
|
@@ -36,7 +36,7 @@ export type PathValue<
|
|
|
36
36
|
? T[P]
|
|
37
37
|
: never;
|
|
38
38
|
|
|
39
|
-
export type FilterOperator = (typeof
|
|
39
|
+
export type FilterOperator = (typeof FILTER_OPERATORS)[number];
|
|
40
40
|
|
|
41
41
|
export interface Filter<TField = string, TValue = unknown> {
|
|
42
42
|
field: TField;
|
|
@@ -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
|
+
}
|
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
|
}
|
package/tests/criteria.test.ts
CHANGED
|
@@ -113,6 +113,14 @@ describe("Criteria", () => {
|
|
|
113
113
|
expect(result.data.every((u) => u.status === "inactive")).toBe(true);
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
it("should filter by search", () => {
|
|
117
|
+
const criteria = Criteria.create<TestUser>().search(["name"], "Bob");
|
|
118
|
+
|
|
119
|
+
const result = PaginatedResult.fromArray(testUsers, criteria);
|
|
120
|
+
expect(result.data).toHaveLength(1);
|
|
121
|
+
expect(result.data[0].name).toBe("Bob");
|
|
122
|
+
});
|
|
123
|
+
|
|
116
124
|
it("should filter by greaterThan", () => {
|
|
117
125
|
const criteria = Criteria.create<TestUser>().where(
|
|
118
126
|
"age",
|