@rineex/ddd 1.6.1 → 2.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/README.md +191 -19
- package/dist/index.d.mts +294 -73
- package/dist/index.d.ts +294 -73
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -844,41 +844,185 @@ export abstract class ApplicationError extends Error {
|
|
|
844
844
|
#### `Result<T, E>`
|
|
845
845
|
|
|
846
846
|
Represents the outcome of an operation that can either succeed or fail, without
|
|
847
|
-
relying on exceptions for control flow.
|
|
847
|
+
relying on exceptions for control flow. This is a functional programming pattern
|
|
848
|
+
that makes error handling explicit in the type system and is commonly used in
|
|
849
|
+
Domain-Driven Design to represent domain operation outcomes.
|
|
850
|
+
|
|
851
|
+
**Key Features:**
|
|
852
|
+
|
|
853
|
+
- **Immutable**: Result instances are frozen to prevent accidental mutations
|
|
854
|
+
- **Type-Safe**: Full TypeScript support with generic types
|
|
855
|
+
- **Explicit Error Handling**: No hidden exceptions, all errors are explicit
|
|
856
|
+
- **DomainError Integration**: Works seamlessly with `DomainError` by default
|
|
848
857
|
|
|
849
858
|
```typescript
|
|
850
|
-
export class Result<T, E> {
|
|
859
|
+
export class Result<T, E = DomainError> {
|
|
851
860
|
readonly isSuccess: boolean;
|
|
852
861
|
readonly isFailure: boolean;
|
|
853
862
|
|
|
854
|
-
public static ok<T, E>(value: T): Result<T, E>;
|
|
855
|
-
public static fail<T, E>(error: E): Result<T, E>;
|
|
856
|
-
public getValue(): T;
|
|
857
|
-
public getError(): E;
|
|
863
|
+
public static ok<T, E = never>(value: T): Result<T, E>;
|
|
864
|
+
public static fail<T = never, E = DomainError>(error: E): Result<T, E>;
|
|
865
|
+
public getValue(): T | undefined;
|
|
866
|
+
public getError(): E | undefined;
|
|
867
|
+
public isSuccessResult(): this is Result<T, never>;
|
|
868
|
+
public isFailureResult(): this is Result<never, E>;
|
|
858
869
|
}
|
|
859
870
|
```
|
|
860
871
|
|
|
861
|
-
**Example:**
|
|
872
|
+
**Basic Example:**
|
|
862
873
|
|
|
863
874
|
```typescript
|
|
864
|
-
import { Result } from '@rineex/ddd';
|
|
875
|
+
import { Result, DomainError } from '@rineex/ddd';
|
|
865
876
|
|
|
866
|
-
|
|
877
|
+
class InvalidValueError extends DomainError {
|
|
878
|
+
public get code() {
|
|
879
|
+
return 'DOMAIN.INVALID_VALUE' as const;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
constructor(message: string) {
|
|
883
|
+
super({ message });
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function parseNumber(input: string): Result<number, DomainError> {
|
|
867
888
|
const value = Number(input);
|
|
868
889
|
if (Number.isNaN(value)) {
|
|
869
|
-
return Result.fail('Invalid number');
|
|
890
|
+
return Result.fail(new InvalidValueError('Invalid number'));
|
|
870
891
|
}
|
|
871
892
|
return Result.ok(value);
|
|
872
893
|
}
|
|
873
894
|
|
|
874
895
|
const result = parseNumber('42');
|
|
875
896
|
if (result.isSuccess) {
|
|
876
|
-
|
|
897
|
+
const value = result.getValue(); // number | undefined
|
|
898
|
+
console.log(value); // 42
|
|
877
899
|
} else {
|
|
878
|
-
|
|
900
|
+
const error = result.getError(); // DomainError | undefined
|
|
901
|
+
console.error(error?.message);
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
**Validation Pattern:**
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
function validateAge(age: number): Result<number, DomainError> {
|
|
909
|
+
if (age < 0) {
|
|
910
|
+
return Result.fail(new InvalidValueError('Age cannot be negative'));
|
|
911
|
+
}
|
|
912
|
+
if (age > 150) {
|
|
913
|
+
return Result.fail(new InvalidValueError('Age seems unrealistic'));
|
|
914
|
+
}
|
|
915
|
+
return Result.ok(age);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const result = validateAge(25);
|
|
919
|
+
if (result.isSuccess) {
|
|
920
|
+
console.log('Valid age:', result.getValue());
|
|
921
|
+
}
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
**Chaining Pattern:**
|
|
925
|
+
|
|
926
|
+
```typescript
|
|
927
|
+
function validateEmail(email: string): Result<string, DomainError> {
|
|
928
|
+
if (!email.includes('@')) {
|
|
929
|
+
return Result.fail(new InvalidValueError('Invalid email format'));
|
|
930
|
+
}
|
|
931
|
+
return Result.ok(email);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function createAccount(email: string): Result<{ email: string }, DomainError> {
|
|
935
|
+
const emailResult = validateEmail(email);
|
|
936
|
+
if (emailResult.isFailureResult()) {
|
|
937
|
+
return emailResult; // Forward the error
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const validatedEmail = emailResult.getValue()!;
|
|
941
|
+
return Result.ok({ email: validatedEmail });
|
|
942
|
+
}
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
**Using Type Guards:**
|
|
946
|
+
|
|
947
|
+
The `isSuccessResult()` and `isFailureResult()` methods provide type-safe
|
|
948
|
+
narrowing:
|
|
949
|
+
|
|
950
|
+
```typescript
|
|
951
|
+
function processResult<T>(result: Result<T, DomainError>): void {
|
|
952
|
+
if (result.isSuccessResult()) {
|
|
953
|
+
// TypeScript narrows result to Result<T, never>
|
|
954
|
+
const value = result.getValue();
|
|
955
|
+
if (value) {
|
|
956
|
+
// Process the value with full type safety
|
|
957
|
+
console.log('Success:', value);
|
|
958
|
+
}
|
|
959
|
+
} else if (result.isFailureResult()) {
|
|
960
|
+
// TypeScript narrows result to Result<never, DomainError>
|
|
961
|
+
const error = result.getError();
|
|
962
|
+
if (error) {
|
|
963
|
+
// Access error properties with full type safety
|
|
964
|
+
console.error('Error code:', error.code);
|
|
965
|
+
console.error('Error message:', error.message);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
879
968
|
}
|
|
880
969
|
```
|
|
881
970
|
|
|
971
|
+
**Working with Domain Errors:**
|
|
972
|
+
|
|
973
|
+
```typescript
|
|
974
|
+
class InvalidStateError extends DomainError {
|
|
975
|
+
public get code() {
|
|
976
|
+
return 'DOMAIN.INVALID_STATE' as const;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
constructor(
|
|
980
|
+
message: string,
|
|
981
|
+
metadata?: Record<string, boolean | number | string>,
|
|
982
|
+
) {
|
|
983
|
+
super({ message, metadata });
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function processOrder(orderId: string): Result<Order, DomainError> {
|
|
988
|
+
const order = orderRepository.findById(orderId);
|
|
989
|
+
if (!order) {
|
|
990
|
+
return Result.fail(new InvalidValueError('Order not found', { orderId }));
|
|
991
|
+
}
|
|
992
|
+
if (order.status !== 'PENDING') {
|
|
993
|
+
return Result.fail(
|
|
994
|
+
new InvalidStateError('Order cannot be processed', {
|
|
995
|
+
orderId,
|
|
996
|
+
currentStatus: order.status,
|
|
997
|
+
}),
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
return Result.ok(order);
|
|
1001
|
+
}
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
**Void Operations:**
|
|
1005
|
+
|
|
1006
|
+
```typescript
|
|
1007
|
+
function deleteUser(id: number): Result<void, DomainError> {
|
|
1008
|
+
if (id <= 0) {
|
|
1009
|
+
return Result.fail(new InvalidValueError('Invalid user ID'));
|
|
1010
|
+
}
|
|
1011
|
+
// ... deletion logic ...
|
|
1012
|
+
return Result.ok(undefined);
|
|
1013
|
+
}
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
**Best Practices:**
|
|
1017
|
+
|
|
1018
|
+
1. Always check `isSuccess` or `isFailure` before calling `getValue()` or
|
|
1019
|
+
`getError()`
|
|
1020
|
+
2. Use `isSuccessResult()` and `isFailureResult()` type guards for better type
|
|
1021
|
+
narrowing when you need TypeScript to narrow the result type
|
|
1022
|
+
3. Use `DomainError` for domain-specific errors to maintain consistency
|
|
1023
|
+
4. Forward errors in chaining operations rather than creating new ones
|
|
1024
|
+
5. Leverage TypeScript's type narrowing for safe value extraction
|
|
1025
|
+
|
|
882
1026
|
### Domain Violations
|
|
883
1027
|
|
|
884
1028
|
#### `DomainViolation`
|
|
@@ -1453,8 +1597,23 @@ try {
|
|
|
1453
1597
|
}
|
|
1454
1598
|
}
|
|
1455
1599
|
|
|
1456
|
-
// Using Result type (functional approach)
|
|
1457
|
-
|
|
1600
|
+
// Using Result type (functional approach with DomainError)
|
|
1601
|
+
class InvalidEmailError extends DomainError {
|
|
1602
|
+
public get code() {
|
|
1603
|
+
return 'DOMAIN.INVALID_VALUE' as const;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
constructor(message: string) {
|
|
1607
|
+
super({ message });
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
function createUser(email: string): Result<User, DomainError> {
|
|
1612
|
+
// Validate email format
|
|
1613
|
+
if (!email.includes('@')) {
|
|
1614
|
+
return Result.fail(new InvalidEmailError('Invalid email format'));
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1458
1617
|
try {
|
|
1459
1618
|
const emailVO = Email.create(email);
|
|
1460
1619
|
const user = new User({
|
|
@@ -1464,19 +1623,32 @@ function createUser(email: string): Result<User, string> {
|
|
|
1464
1623
|
});
|
|
1465
1624
|
return Result.ok(user);
|
|
1466
1625
|
} catch (error) {
|
|
1626
|
+
if (error instanceof InvalidValueObjectError) {
|
|
1627
|
+
return Result.fail(new InvalidEmailError(error.message));
|
|
1628
|
+
}
|
|
1467
1629
|
return Result.fail(
|
|
1468
|
-
|
|
1630
|
+
new InvalidEmailError(
|
|
1631
|
+
error instanceof Error ? error.message : 'Unknown error',
|
|
1632
|
+
),
|
|
1469
1633
|
);
|
|
1470
1634
|
}
|
|
1471
1635
|
}
|
|
1472
1636
|
|
|
1473
1637
|
const result = createUser('user@example.com');
|
|
1474
|
-
if (result.
|
|
1638
|
+
if (result.isSuccessResult()) {
|
|
1475
1639
|
const user = result.getValue();
|
|
1476
|
-
|
|
1477
|
-
|
|
1640
|
+
if (user) {
|
|
1641
|
+
// Use user safely with full type safety
|
|
1642
|
+
console.log('User created:', user.id.toString());
|
|
1643
|
+
}
|
|
1644
|
+
} else if (result.isFailureResult()) {
|
|
1478
1645
|
const error = result.getError();
|
|
1479
|
-
|
|
1646
|
+
if (error) {
|
|
1647
|
+
// Handle error with full context and type safety
|
|
1648
|
+
console.error('Error code:', error.code);
|
|
1649
|
+
console.error('Error message:', error.message);
|
|
1650
|
+
console.error('Metadata:', error.metadata);
|
|
1651
|
+
}
|
|
1480
1652
|
}
|
|
1481
1653
|
```
|
|
1482
1654
|
|