@woltz/rich-domain 1.9.1 → 1.9.2
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/cjs/core/aggregate-changes.d.ts +14 -0
- package/dist/cjs/core/aggregate-changes.d.ts.map +1 -1
- package/dist/cjs/core/aggregate-changes.js +18 -0
- package/dist/cjs/core/aggregate-changes.js.map +1 -1
- package/dist/cjs/core/base-entity.d.ts +2 -0
- package/dist/cjs/core/base-entity.d.ts.map +1 -1
- package/dist/cjs/core/base-entity.js +39 -41
- package/dist/cjs/core/base-entity.js.map +1 -1
- package/dist/cjs/core/change-tracker.d.ts +8 -0
- package/dist/cjs/core/change-tracker.d.ts.map +1 -1
- package/dist/cjs/core/change-tracker.js +36 -6
- package/dist/cjs/core/change-tracker.js.map +1 -1
- package/dist/cjs/core/value-object.d.ts.map +1 -1
- package/dist/cjs/core/value-object.js +3 -5
- package/dist/cjs/core/value-object.js.map +1 -1
- package/dist/cjs/repository/entity-schema-registry.d.ts +56 -3
- package/dist/cjs/repository/entity-schema-registry.d.ts.map +1 -1
- package/dist/cjs/repository/entity-schema-registry.js +61 -6
- package/dist/cjs/repository/entity-schema-registry.js.map +1 -1
- package/dist/cjs/utils/helpers.d.ts +1 -0
- package/dist/cjs/utils/helpers.d.ts.map +1 -1
- package/dist/cjs/utils/helpers.js +4 -0
- package/dist/cjs/utils/helpers.js.map +1 -1
- package/dist/esm/core/aggregate-changes.d.ts +14 -0
- package/dist/esm/core/aggregate-changes.d.ts.map +1 -1
- package/dist/esm/core/aggregate-changes.js +18 -0
- package/dist/esm/core/aggregate-changes.js.map +1 -1
- package/dist/esm/core/base-entity.d.ts +2 -0
- package/dist/esm/core/base-entity.d.ts.map +1 -1
- package/dist/esm/core/base-entity.js +37 -39
- package/dist/esm/core/base-entity.js.map +1 -1
- package/dist/esm/core/change-tracker.d.ts +8 -0
- package/dist/esm/core/change-tracker.d.ts.map +1 -1
- package/dist/esm/core/change-tracker.js +36 -6
- package/dist/esm/core/change-tracker.js.map +1 -1
- package/dist/esm/core/value-object.d.ts.map +1 -1
- package/dist/esm/core/value-object.js +1 -3
- package/dist/esm/core/value-object.js.map +1 -1
- package/dist/esm/repository/entity-schema-registry.d.ts +56 -3
- package/dist/esm/repository/entity-schema-registry.d.ts.map +1 -1
- package/dist/esm/repository/entity-schema-registry.js +61 -6
- package/dist/esm/repository/entity-schema-registry.js.map +1 -1
- package/dist/esm/utils/helpers.d.ts +1 -0
- package/dist/esm/utils/helpers.d.ts.map +1 -1
- package/dist/esm/utils/helpers.js +3 -0
- package/dist/esm/utils/helpers.js.map +1 -1
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/core/aggregate-changes.d.ts +14 -0
- package/dist/types/core/aggregate-changes.d.ts.map +1 -1
- package/dist/types/core/base-entity.d.ts +2 -0
- package/dist/types/core/base-entity.d.ts.map +1 -1
- package/dist/types/core/change-tracker.d.ts +8 -0
- package/dist/types/core/change-tracker.d.ts.map +1 -1
- package/dist/types/core/value-object.d.ts.map +1 -1
- package/dist/types/repository/entity-schema-registry.d.ts +56 -3
- package/dist/types/repository/entity-schema-registry.d.ts.map +1 -1
- package/dist/types/utils/helpers.d.ts +1 -0
- package/dist/types/utils/helpers.d.ts.map +1 -1
- package/package.json +68 -67
- package/src/constants.ts +82 -0
- package/src/core/aggregate-changes.ts +466 -0
- package/src/core/base-aggregate.ts +76 -0
- package/src/core/base-entity.ts +552 -0
- package/src/core/change-tracker.ts +1327 -0
- package/src/core/domain-event.ts +41 -0
- package/src/core/entity-changes.ts +146 -0
- package/src/core/entity.ts +13 -0
- package/src/core/id.ts +124 -0
- package/src/core/index.ts +9 -0
- package/src/core/value-object.ts +179 -0
- package/src/criteria.ts +574 -0
- package/src/exceptions.ts +549 -0
- package/src/index.ts +74 -0
- package/src/repository/base-repository.ts +81 -0
- package/src/repository/entity-schema-registry.ts +620 -0
- package/src/repository/index.ts +5 -0
- package/src/repository/mapper.ts +7 -0
- package/src/repository/paginated-result.ts +251 -0
- package/src/repository/unit-of-work.ts +76 -0
- package/src/types/change-tracker.ts +268 -0
- package/src/types/criteria.ts +197 -0
- package/src/types/domain-event.ts +29 -0
- package/src/types/domain.ts +41 -0
- package/src/types/event-bus.ts +17 -0
- package/src/types/index.ts +9 -0
- package/src/types/outbox-store.ts +97 -0
- package/src/types/standard-schema.ts +19 -0
- package/src/types/unit-of-work.ts +46 -0
- package/src/types/utils.ts +24 -0
- package/src/utils/criteria-operator-validation.ts +209 -0
- package/src/utils/crypto.ts +31 -0
- package/src/utils/helpers.ts +50 -0
- package/src/validation-error.ts +219 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const customCrypto = {
|
|
2
|
+
randomUUID: () => {
|
|
3
|
+
if (
|
|
4
|
+
typeof globalThis !== "undefined" &&
|
|
5
|
+
globalThis?.crypto &&
|
|
6
|
+
typeof globalThis.crypto.randomUUID === "function"
|
|
7
|
+
) {
|
|
8
|
+
return globalThis.crypto.randomUUID();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const hexChars = "0123456789abcdef";
|
|
12
|
+
let uuid = "";
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < 36; i++) {
|
|
15
|
+
if (i === 8 || i === 13 || i === 18 || i === 23) {
|
|
16
|
+
uuid += "-";
|
|
17
|
+
} else if (i === 14) {
|
|
18
|
+
uuid += "4";
|
|
19
|
+
} else if (i === 19) {
|
|
20
|
+
uuid += hexChars.charAt(Math.floor(Math.random() * 4) + 8);
|
|
21
|
+
} else {
|
|
22
|
+
uuid += hexChars.charAt(Math.floor(Math.random() * 16));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return uuid;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const UUID = customCrypto.randomUUID;
|
|
31
|
+
export default UUID;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function parseQueryValue(value: unknown): any {
|
|
2
|
+
if (value === null || value === undefined) return value;
|
|
3
|
+
if (typeof value === "boolean") return value;
|
|
4
|
+
if (typeof value === "number") return value;
|
|
5
|
+
if (value instanceof Date) return value;
|
|
6
|
+
if (Array.isArray(value)) return value;
|
|
7
|
+
if (typeof value === "object") return value;
|
|
8
|
+
|
|
9
|
+
const str = String(value);
|
|
10
|
+
if (str === "true" || str === "false") return str === "true";
|
|
11
|
+
if (str.trim() !== "" && !isNaN(Number(str))) return Number(str);
|
|
12
|
+
if (!isNaN(Date.parse(str))) return new Date(str);
|
|
13
|
+
|
|
14
|
+
return str;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function levenshteinDistance(a: string, b: string): number {
|
|
18
|
+
const matrix: number[][] = [];
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i <= b.length; i++) {
|
|
21
|
+
matrix[i] = [i];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (let j = 0; j <= a.length; j++) {
|
|
25
|
+
matrix[0][j] = j;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (let i = 1; i <= b.length; i++) {
|
|
29
|
+
for (let j = 1; j <= a.length; j++) {
|
|
30
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
31
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
32
|
+
} else {
|
|
33
|
+
matrix[i][j] = Math.min(
|
|
34
|
+
matrix[i - 1][j - 1] + 1, // substitution
|
|
35
|
+
matrix[i][j - 1] + 1, // insertion
|
|
36
|
+
matrix[i - 1][j] + 1 // deletion
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return matrix[b.length][a.length];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getStaticProperty<T>(
|
|
46
|
+
instance: any,
|
|
47
|
+
propertyName: string
|
|
48
|
+
): T | undefined {
|
|
49
|
+
return instance.constructor[propertyName];
|
|
50
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
export interface ValidationIssue {
|
|
2
|
+
path: string[];
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type FormattedValidationError = {
|
|
7
|
+
path: string;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class ValidationIssueCollector {
|
|
12
|
+
private issues: ValidationIssue[] = [];
|
|
13
|
+
|
|
14
|
+
add(path: string | string[], message: string): void {
|
|
15
|
+
this.issues.push(createValidationIssue(path, message));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getIssues(): readonly ValidationIssue[] {
|
|
19
|
+
return this.issues;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hasIssues(): boolean {
|
|
23
|
+
return this.issues.length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
clear(): void {
|
|
27
|
+
this.issues = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
toValidationError(options?: {
|
|
31
|
+
entityName?: string;
|
|
32
|
+
}): ValidationError | undefined {
|
|
33
|
+
if (!this.hasIssues()) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
return new ValidationError([...this.issues], options);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class ValidationError extends Error {
|
|
41
|
+
public readonly issues: ValidationIssue[];
|
|
42
|
+
public readonly __isValidationError = true;
|
|
43
|
+
public readonly entityName?: string;
|
|
44
|
+
|
|
45
|
+
constructor(
|
|
46
|
+
issues: ValidationIssue[],
|
|
47
|
+
options?: { message?: string; entityName?: string }
|
|
48
|
+
) {
|
|
49
|
+
const errorMessage =
|
|
50
|
+
options?.message ||
|
|
51
|
+
ValidationError.formatMessage(issues, options?.entityName);
|
|
52
|
+
super(errorMessage);
|
|
53
|
+
this.name = "ValidationError";
|
|
54
|
+
this.issues = issues;
|
|
55
|
+
this.entityName = options?.entityName;
|
|
56
|
+
|
|
57
|
+
if (Error.captureStackTrace) {
|
|
58
|
+
Error.captureStackTrace(this, ValidationError);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static fromIssues(
|
|
63
|
+
issues: ValidationIssue[],
|
|
64
|
+
options?: { message?: string; entityName?: string }
|
|
65
|
+
): ValidationError {
|
|
66
|
+
return new ValidationError(issues, options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static merge(
|
|
70
|
+
existing: ValidationError | undefined,
|
|
71
|
+
extra: ValidationIssue[],
|
|
72
|
+
options?: { entityName?: string }
|
|
73
|
+
): ValidationError | undefined {
|
|
74
|
+
if (!existing && extra.length === 0) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const allIssues = [...(existing?.issues ?? []), ...extra];
|
|
79
|
+
return new ValidationError(allIssues, {
|
|
80
|
+
entityName: options?.entityName ?? existing?.entityName,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private static formatMessage(
|
|
85
|
+
issues: ValidationIssue[],
|
|
86
|
+
entityName?: string
|
|
87
|
+
): string {
|
|
88
|
+
const entityPrefix = entityName ? `[${entityName}] ` : "";
|
|
89
|
+
|
|
90
|
+
if (issues.length === 0) {
|
|
91
|
+
return `${entityPrefix}Validation failed`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (issues.length === 1) {
|
|
95
|
+
const issue = issues[0];
|
|
96
|
+
const pathStr =
|
|
97
|
+
issue.path.length > 0 ? ` at "${issue.path.join(".")}"` : "";
|
|
98
|
+
return `${entityPrefix}Validation failed${pathStr}: ${issue.message}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const errorLines = issues
|
|
102
|
+
.map((issue, index) => {
|
|
103
|
+
const pathStr =
|
|
104
|
+
issue.path.length > 0 ? ` at "${issue.path.join(".")}"` : "";
|
|
105
|
+
return ` ${index + 1}. ${issue.message}${pathStr}`;
|
|
106
|
+
})
|
|
107
|
+
.join("\n");
|
|
108
|
+
|
|
109
|
+
return `${entityPrefix}Validation failed with ${issues.length} error(s):\n${errorLines}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private static normalizePath(path: string | string[]): string {
|
|
113
|
+
return Array.isArray(path) ? path.join(".") : path;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if an error is a ValidationError (works across module boundaries)
|
|
118
|
+
*/
|
|
119
|
+
static isValidationError(error: unknown): error is ValidationError {
|
|
120
|
+
if (error instanceof ValidationError) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return (
|
|
124
|
+
error instanceof Error &&
|
|
125
|
+
error.name === "ValidationError" &&
|
|
126
|
+
"issues" in error &&
|
|
127
|
+
Array.isArray((error as any).issues)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get all error messages as a simple array
|
|
133
|
+
*/
|
|
134
|
+
getMessages(): string[] {
|
|
135
|
+
return this.issues.map((i) => i.message);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get errors for a specific field path
|
|
140
|
+
*/
|
|
141
|
+
getErrorsForPath(path: string | string[]): ValidationIssue[] {
|
|
142
|
+
const normalized = ValidationError.normalizePath(path);
|
|
143
|
+
return this.issues.filter((i) => i.path.join(".") === normalized);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if a specific path has errors
|
|
148
|
+
*/
|
|
149
|
+
hasErrorsForPath(path: string | string[]): boolean {
|
|
150
|
+
return this.getErrorsForPath(path).length > 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Convert to a plain object for serialization
|
|
155
|
+
*/
|
|
156
|
+
toJSON(): {
|
|
157
|
+
name: string;
|
|
158
|
+
message: string;
|
|
159
|
+
issues: ValidationIssue[];
|
|
160
|
+
entityName?: string;
|
|
161
|
+
} {
|
|
162
|
+
return {
|
|
163
|
+
name: this.name,
|
|
164
|
+
message: this.message,
|
|
165
|
+
issues: this.issues,
|
|
166
|
+
entityName: this.entityName,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get validation errors formatted for UI/API consumption
|
|
172
|
+
*/
|
|
173
|
+
getFormattedErrors(): FormattedValidationError[] {
|
|
174
|
+
return this.issues.map((issue) => ({
|
|
175
|
+
path: issue.path.length > 0 ? issue.path.join(".") : "",
|
|
176
|
+
message: issue.message,
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get a summary of the error for logging
|
|
182
|
+
*/
|
|
183
|
+
getSummary(): string {
|
|
184
|
+
const entityPrefix = this.entityName ? `[${this.entityName}] ` : "";
|
|
185
|
+
const paths = this.issues
|
|
186
|
+
.filter((i) => i.path.length > 0)
|
|
187
|
+
.map((i) => i.path.join("."));
|
|
188
|
+
|
|
189
|
+
if (paths.length === 0) {
|
|
190
|
+
return `${entityPrefix}Validation failed with ${this.issues.length} error(s)`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const uniquePaths = Array.from(new Set(paths));
|
|
194
|
+
return `${entityPrefix}Validation failed on: ${uniquePaths.join(", ")} (${this.issues.length} error(s))`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Helper to create a single validation issue
|
|
200
|
+
*/
|
|
201
|
+
export function createValidationIssue(
|
|
202
|
+
path: string | string[],
|
|
203
|
+
message: string
|
|
204
|
+
): ValidationIssue {
|
|
205
|
+
return {
|
|
206
|
+
path: Array.isArray(path) ? path : path.split("."),
|
|
207
|
+
message,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Helper to throw a validation error with a single issue
|
|
213
|
+
*/
|
|
214
|
+
export function throwValidationError(
|
|
215
|
+
path: string | string[],
|
|
216
|
+
message: string
|
|
217
|
+
): never {
|
|
218
|
+
throw new ValidationError([createValidationIssue(path, message)]);
|
|
219
|
+
}
|