@emkodev/emkore 1.0.3
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 +269 -0
- package/DEVELOPER_GUIDE.md +227 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/bun.lock +22 -0
- package/example/README.md +200 -0
- package/example/create-user.interactor.ts +88 -0
- package/example/dto/user.dto.ts +34 -0
- package/example/entity/user.entity.ts +54 -0
- package/example/index.ts +18 -0
- package/example/interface/create-user.usecase.ts +93 -0
- package/example/interface/user.repository.ts +23 -0
- package/mod.ts +1 -0
- package/package.json +32 -0
- package/src/common/abstract.actor.ts +59 -0
- package/src/common/abstract.entity.ts +59 -0
- package/src/common/abstract.interceptor.ts +17 -0
- package/src/common/abstract.repository.ts +162 -0
- package/src/common/abstract.usecase.ts +113 -0
- package/src/common/config/config-registry.ts +190 -0
- package/src/common/config/config-section.ts +106 -0
- package/src/common/exception/authorization-exception.ts +28 -0
- package/src/common/exception/repository-exception.ts +46 -0
- package/src/common/interceptor/audit-log.interceptor.ts +181 -0
- package/src/common/interceptor/authorization.interceptor.ts +252 -0
- package/src/common/interceptor/performance.interceptor.ts +101 -0
- package/src/common/llm/api-definition.type.ts +185 -0
- package/src/common/pattern/unit-of-work.ts +78 -0
- package/src/common/platform/env.ts +38 -0
- package/src/common/registry/usecase-registry.ts +80 -0
- package/src/common/type/interceptor-context.type.ts +25 -0
- package/src/common/type/json-schema.type.ts +80 -0
- package/src/common/type/json.type.ts +5 -0
- package/src/common/type/lowercase.type.ts +48 -0
- package/src/common/type/metadata.type.ts +5 -0
- package/src/common/type/money.class.ts +384 -0
- package/src/common/type/permission.type.ts +43 -0
- package/src/common/validation/validation-result.ts +52 -0
- package/src/common/validation/validators.ts +441 -0
- package/src/index.ts +95 -0
- package/test/unit/abstract-actor.test.ts +608 -0
- package/test/unit/actor.test.ts +89 -0
- package/test/unit/api-definition.test.ts +628 -0
- package/test/unit/authorization.test.ts +101 -0
- package/test/unit/entity.test.ts +95 -0
- package/test/unit/money.test.ts +480 -0
- package/test/unit/validation.test.ts +138 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cents - Monetary amount stored as an integer in minor units (cents).
|
|
3
|
+
*
|
|
4
|
+
* All monetary values MUST be stored in cents (or equivalent minor units) to avoid
|
|
5
|
+
* floating-point precision errors in calculations (e.g., 0.1 + 0.2 = 0.30000000000004).
|
|
6
|
+
*
|
|
7
|
+
* Use this type in DTOs, value objects, and anywhere monetary amounts are stored or transmitted.
|
|
8
|
+
*
|
|
9
|
+
* @example USD: 14999 cents = $149.99
|
|
10
|
+
* @example EUR: 9999 cents = €99.99
|
|
11
|
+
* @example JPY: 1000000 cents = ¥10,000 (note: JPY has no minor units, but we store uniformly)
|
|
12
|
+
*
|
|
13
|
+
* @see {@link Money} - For type-safe monetary calculations with currency support
|
|
14
|
+
*/
|
|
15
|
+
export type Cents = number;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Money - Type-safe monetary value with currency support.
|
|
19
|
+
*
|
|
20
|
+
* Immutable value object for monetary calculations that:
|
|
21
|
+
* - Stores amounts in cents (minor units) to avoid floating-point errors
|
|
22
|
+
* - Enforces currency consistency in arithmetic operations
|
|
23
|
+
* - Provides locale-aware formatting with Intl.NumberFormat
|
|
24
|
+
* - Supports all common monetary operations (add, subtract, multiply, divide)
|
|
25
|
+
* - Validates currency mixing (throws error on USD + EUR operations)
|
|
26
|
+
* - Returns new instances for all operations (immutability)
|
|
27
|
+
*
|
|
28
|
+
* **Architecture:**
|
|
29
|
+
* - DTOs store raw cents as `Cents` type (number)
|
|
30
|
+
* - Entities use Money class for business logic
|
|
31
|
+
* - Frontend receives DTOs, converts to Money for calculations
|
|
32
|
+
*
|
|
33
|
+
* **Critical Rules:**
|
|
34
|
+
* - ALWAYS store monetary values in cents, never in dollars/euros/etc
|
|
35
|
+
* - NEVER use floating-point numbers for money (0.1 + 0.2 ≠ 0.3)
|
|
36
|
+
* - ALWAYS explicitly specify currency - no defaults to prevent bugs
|
|
37
|
+
* - ALWAYS use Money class for calculations involving currency
|
|
38
|
+
* - DTOs use `Cents` type, business logic uses `Money` class
|
|
39
|
+
*
|
|
40
|
+
* @example Basic usage
|
|
41
|
+
* ```ts
|
|
42
|
+
* const price = Money.fromCents(14999, "USD"); // $149.99
|
|
43
|
+
* const tax = price.multiply(0.08); // 8% tax
|
|
44
|
+
* const total = price.add(tax); // $161.99
|
|
45
|
+
* console.log(total.format()); // "$161.99"
|
|
46
|
+
* console.log(total.cents); // 16199
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example Currency safety
|
|
50
|
+
* ```ts
|
|
51
|
+
* const usd = Money.fromCents(100, "USD");
|
|
52
|
+
* const eur = Money.fromCents(100, "EUR");
|
|
53
|
+
* usd.add(eur); // ❌ Throws: "Cannot operate on Money with different currencies"
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example Multi-currency formatting
|
|
57
|
+
* ```ts
|
|
58
|
+
* const price = Money.fromCents(14999, "EUR");
|
|
59
|
+
* price.format("en-US"); // "€149.99"
|
|
60
|
+
* price.format("de-DE"); // "149,99 €"
|
|
61
|
+
* price.formatNumber(); // "149.99" (no symbol)
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @example Discount calculations
|
|
65
|
+
* ```ts
|
|
66
|
+
* const price = Money.fromCents(10000, "USD"); // $100.00
|
|
67
|
+
* const discount = price.multiply(0.2); // 20% off = $20.00
|
|
68
|
+
* const final = price.subtract(discount); // $80.00
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @see {@link Cents} - Type alias for raw cent values in DTOs
|
|
72
|
+
*/
|
|
73
|
+
export class Money {
|
|
74
|
+
private readonly _value: Cents;
|
|
75
|
+
private readonly _currency: string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create Money from cents (minor units).
|
|
79
|
+
*
|
|
80
|
+
* Currency is REQUIRED to prevent accidental currency mixing bugs.
|
|
81
|
+
* This forces explicit declaration of currency on every Money instance.
|
|
82
|
+
*
|
|
83
|
+
* @param cents - Amount in cents (integer, will be rounded if fractional)
|
|
84
|
+
* @param currency - ISO 4217 currency code (REQUIRED - e.g., "USD", "EUR", "JPY")
|
|
85
|
+
* @throws {Error} If cents is null or undefined
|
|
86
|
+
* @throws {Error} If currency is empty or not a string
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const usd = new Money(14999, "USD"); // $149.99
|
|
91
|
+
* const euro = new Money(9999, "EUR"); // €99.99
|
|
92
|
+
* const yen = new Money(1000000, "JPY"); // ¥10,000
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
constructor(cents: Cents | null | undefined, currency: string) {
|
|
96
|
+
if (cents == null) {
|
|
97
|
+
throw new Error("Money value cannot be null - amount is required");
|
|
98
|
+
}
|
|
99
|
+
if (!currency || typeof currency !== "string") {
|
|
100
|
+
throw new Error("Currency must be a non-empty string (ISO 4217 code)");
|
|
101
|
+
}
|
|
102
|
+
this._value = Math.round(cents); // Ensure integer cents
|
|
103
|
+
this._currency = currency.toUpperCase();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create Money from cents (minor units).
|
|
108
|
+
* Alias for constructor for clarity.
|
|
109
|
+
*
|
|
110
|
+
* @param cents - Amount in cents (integer)
|
|
111
|
+
* @param currency - ISO 4217 currency code (REQUIRED)
|
|
112
|
+
* @returns Money instance
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const usd = Money.fromCents(14999, "USD"); // $149.99
|
|
117
|
+
* const eur = Money.fromCents(9999, "EUR"); // €99.99
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
static fromCents(cents: Cents, currency: string): Money {
|
|
121
|
+
return new Money(cents, currency);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create Money from major units (dollars, euros, etc.).
|
|
126
|
+
*
|
|
127
|
+
* WARNING: Subject to floating-point precision issues.
|
|
128
|
+
* Prefer fromCents when possible.
|
|
129
|
+
*
|
|
130
|
+
* @param amount - Amount in major units (e.g., 149.99 for $149.99)
|
|
131
|
+
* @param currency - ISO 4217 currency code (REQUIRED)
|
|
132
|
+
* @returns Money instance
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const price = Money.fromMajorUnits(149.99, "USD"); // 14999 cents = $149.99
|
|
137
|
+
* const euro = Money.fromMajorUnits(99.50, "EUR"); // 9950 cents = €99.50
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
static fromMajorUnits(amount: number, currency: string): Money {
|
|
141
|
+
return new Money(Math.round(amount * 100), currency);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create zero Money in the given currency.
|
|
146
|
+
*
|
|
147
|
+
* @param currency - ISO 4217 currency code (REQUIRED)
|
|
148
|
+
* @returns Money instance with zero value
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* const usdTotal = Money.zero("USD"); // $0.00
|
|
153
|
+
* const eurTotal = Money.zero("EUR"); // €0.00
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
static zero(currency: string): Money {
|
|
157
|
+
return new Money(0, currency);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Add two Money amounts.
|
|
162
|
+
* Currencies must match.
|
|
163
|
+
*
|
|
164
|
+
* @throws Error if currencies don't match
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* const a = Money.fromCents(100, "USD");
|
|
168
|
+
* const b = Money.fromCents(50, "USD");
|
|
169
|
+
* const total = a.add(b); // 150 cents
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
add(other: Money): Money {
|
|
173
|
+
this._validateSameCurrency(other);
|
|
174
|
+
return new Money(this._value + other._value, this._currency);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Subtract two Money amounts.
|
|
179
|
+
* Currencies must match.
|
|
180
|
+
*
|
|
181
|
+
* @throws Error if currencies don't match
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const a = Money.fromCents(100, "USD");
|
|
185
|
+
* const b = Money.fromCents(30, "USD");
|
|
186
|
+
* const diff = a.subtract(b); // 70 cents
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
subtract(other: Money): Money {
|
|
190
|
+
this._validateSameCurrency(other);
|
|
191
|
+
return new Money(this._value - other._value, this._currency);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Multiply Money by a number.
|
|
196
|
+
* Result is rounded to nearest cent.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* const price = Money.fromCents(100, "USD");
|
|
201
|
+
* const total = price.multiply(3); // 300 cents
|
|
202
|
+
* const taxed = price.multiply(1.08); // 108 cents (rounded)
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
multiply(multiplier: number): Money {
|
|
206
|
+
return new Money(Math.round(this._value * multiplier), this._currency);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Divide Money by a number.
|
|
211
|
+
* Result is rounded to nearest cent.
|
|
212
|
+
*
|
|
213
|
+
* @throws Error if divisor is zero
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* const total = Money.fromCents(100, "USD");
|
|
217
|
+
* const perItem = total.divide(3); // 33 cents (rounded)
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
divide(divisor: number): Money {
|
|
221
|
+
if (divisor === 0) {
|
|
222
|
+
throw new Error("Cannot divide money by zero");
|
|
223
|
+
}
|
|
224
|
+
return new Money(Math.round(this._value / divisor), this._currency);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check if two Money amounts are equal.
|
|
229
|
+
* Currencies must match.
|
|
230
|
+
*
|
|
231
|
+
* @throws Error if currencies don't match
|
|
232
|
+
*/
|
|
233
|
+
equals(other: Money): boolean {
|
|
234
|
+
this._validateSameCurrency(other);
|
|
235
|
+
return this._value === other._value;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Check if this Money is less than another.
|
|
240
|
+
* Currencies must match.
|
|
241
|
+
*
|
|
242
|
+
* @throws Error if currencies don't match
|
|
243
|
+
*/
|
|
244
|
+
lessThan(other: Money): boolean {
|
|
245
|
+
this._validateSameCurrency(other);
|
|
246
|
+
return this._value < other._value;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if this Money is less than or equal to another.
|
|
251
|
+
* Currencies must match.
|
|
252
|
+
*
|
|
253
|
+
* @throws Error if currencies don't match
|
|
254
|
+
*/
|
|
255
|
+
lessThanOrEqual(other: Money): boolean {
|
|
256
|
+
this._validateSameCurrency(other);
|
|
257
|
+
return this._value <= other._value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if this Money is greater than another.
|
|
262
|
+
* Currencies must match.
|
|
263
|
+
*
|
|
264
|
+
* @throws Error if currencies don't match
|
|
265
|
+
*/
|
|
266
|
+
greaterThan(other: Money): boolean {
|
|
267
|
+
this._validateSameCurrency(other);
|
|
268
|
+
return this._value > other._value;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Check if this Money is greater than or equal to another.
|
|
273
|
+
* Currencies must match.
|
|
274
|
+
*
|
|
275
|
+
* @throws Error if currencies don't match
|
|
276
|
+
*/
|
|
277
|
+
greaterThanOrEqual(other: Money): boolean {
|
|
278
|
+
this._validateSameCurrency(other);
|
|
279
|
+
return this._value >= other._value;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get the amount in cents (minor units).
|
|
284
|
+
*
|
|
285
|
+
* @returns Amount in cents as integer
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* const price = Money.fromCents(14999, "USD");
|
|
290
|
+
* console.log(price.cents); // 14999
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
get cents(): Cents {
|
|
294
|
+
return this._value;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get the currency code (ISO 4217).
|
|
299
|
+
*/
|
|
300
|
+
get currency(): string {
|
|
301
|
+
return this._currency;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Format Money for display with currency symbol.
|
|
306
|
+
*
|
|
307
|
+
* @param locale - BCP 47 language tag (default: "en-US")
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* const price = Money.fromCents(14999, "USD");
|
|
311
|
+
* price.format(); // "$149.99"
|
|
312
|
+
* price.format("de-DE"); // "149,99 $"
|
|
313
|
+
*
|
|
314
|
+
* const euro = Money.fromCents(9999, "EUR");
|
|
315
|
+
* euro.format("en-US"); // "€99.99"
|
|
316
|
+
* euro.format("de-DE"); // "99,99 €"
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
format(locale = "en-US"): string {
|
|
320
|
+
return new Intl.NumberFormat(locale, {
|
|
321
|
+
style: "currency",
|
|
322
|
+
currency: this._currency,
|
|
323
|
+
}).format(this._value / 100);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Format Money as a number without currency symbol.
|
|
328
|
+
*
|
|
329
|
+
* @param locale - BCP 47 language tag (default: "en-US")
|
|
330
|
+
* @example
|
|
331
|
+
* ```ts
|
|
332
|
+
* const price = Money.fromCents(14999, "USD");
|
|
333
|
+
* price.formatNumber(); // "149.99"
|
|
334
|
+
* price.formatNumber("de-DE"); // "149,99"
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
formatNumber(locale = "en-US"): string {
|
|
338
|
+
return new Intl.NumberFormat(locale, {
|
|
339
|
+
minimumFractionDigits: 2,
|
|
340
|
+
maximumFractionDigits: 2,
|
|
341
|
+
}).format(this._value / 100);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Convert to string (major units without currency symbol).
|
|
346
|
+
* For display, prefer format() or formatNumber().
|
|
347
|
+
*/
|
|
348
|
+
toString(): string {
|
|
349
|
+
return (this._value / 100).toString();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Convert to JSON (returns cents as number for storage/API).
|
|
354
|
+
*
|
|
355
|
+
* Use this when serializing Money to DTOs or API responses.
|
|
356
|
+
* The returned value is in cents and should be stored/transmitted as `Cents` type.
|
|
357
|
+
*
|
|
358
|
+
* @returns Amount in cents (integer)
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```ts
|
|
362
|
+
* const price = Money.fromCents(14999, "USD");
|
|
363
|
+
* const dto = {
|
|
364
|
+
* unitPrice: price.toJson(), // 14999 (Cents)
|
|
365
|
+
* currency: price.currency // "USD"
|
|
366
|
+
* };
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
toJson(): Cents {
|
|
370
|
+
return this._value;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Validate that two Money amounts have the same currency.
|
|
375
|
+
* @throws Error if currencies don't match
|
|
376
|
+
*/
|
|
377
|
+
private _validateSameCurrency(other: Money): void {
|
|
378
|
+
if (this._currency !== other._currency) {
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Cannot operate on Money with different currencies: ${this._currency} and ${other._currency}`,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export enum ResourceScope {
|
|
2
|
+
ALL = "all",
|
|
3
|
+
OWNED = "owned",
|
|
4
|
+
TEAM = "team",
|
|
5
|
+
PROJECT = "project",
|
|
6
|
+
BUSINESS = "business",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ResourceConstraints {
|
|
10
|
+
scope?: ResourceScope;
|
|
11
|
+
statuses?: string[];
|
|
12
|
+
timeRestriction?: {
|
|
13
|
+
from: Date;
|
|
14
|
+
to: Date;
|
|
15
|
+
};
|
|
16
|
+
businessId?: string;
|
|
17
|
+
teamId?: string;
|
|
18
|
+
projectId?: string;
|
|
19
|
+
resourceId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Permission {
|
|
23
|
+
readonly resource: string;
|
|
24
|
+
readonly action: string;
|
|
25
|
+
readonly constraints?: ResourceConstraints;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const COMMON_ACTIONS_ARRAY = [
|
|
29
|
+
"create",
|
|
30
|
+
"retrieve",
|
|
31
|
+
"list",
|
|
32
|
+
"count",
|
|
33
|
+
"update",
|
|
34
|
+
"delete",
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export type CommonAction = typeof COMMON_ACTIONS_ARRAY[number];
|
|
38
|
+
|
|
39
|
+
const COMMON_ACTIONS = new Set(COMMON_ACTIONS_ARRAY);
|
|
40
|
+
|
|
41
|
+
export function isCommonAction(action: string): action is CommonAction {
|
|
42
|
+
return COMMON_ACTIONS.has(action as CommonAction);
|
|
43
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class ValidationResult {
|
|
2
|
+
constructor(
|
|
3
|
+
public readonly isValid: boolean,
|
|
4
|
+
public readonly errors: readonly string[] = [],
|
|
5
|
+
public readonly fieldErrors: Readonly<
|
|
6
|
+
Record<string, readonly string[]>
|
|
7
|
+
> = {},
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
static success(): ValidationResult {
|
|
11
|
+
return new ValidationResult(true);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static failure(errors: string[]): ValidationResult {
|
|
15
|
+
return new ValidationResult(false, errors);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static fieldFailure(fieldErrors: Record<string, string[]>): ValidationResult {
|
|
19
|
+
const allErrors = Object.values(fieldErrors).flat();
|
|
20
|
+
return new ValidationResult(false, allErrors, fieldErrors);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
merge(other: ValidationResult): ValidationResult {
|
|
24
|
+
if (this.isValid && other.isValid) {
|
|
25
|
+
return ValidationResult.success();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const mergedErrors = [...this.errors, ...other.errors];
|
|
29
|
+
const mergedFieldErrors: Record<string, readonly string[]> = {
|
|
30
|
+
...this.fieldErrors,
|
|
31
|
+
...other.fieldErrors,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Merge field errors for same fields
|
|
35
|
+
for (const [field, errors] of Object.entries(other.fieldErrors)) {
|
|
36
|
+
if (this.fieldErrors[field]) {
|
|
37
|
+
mergedFieldErrors[field] = [...this.fieldErrors[field], ...errors];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return new ValidationResult(false, mergedErrors, mergedFieldErrors);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
toString(): string {
|
|
45
|
+
if (this.isValid) return "ValidationResult.success()";
|
|
46
|
+
return `ValidationResult.failure(errors: ${
|
|
47
|
+
JSON.stringify(
|
|
48
|
+
this.errors,
|
|
49
|
+
)
|
|
50
|
+
}, fieldErrors: ${JSON.stringify(this.fieldErrors)})`;
|
|
51
|
+
}
|
|
52
|
+
}
|