@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 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
- function parseNumber(input: string): Result<number, string> {
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
- console.log(result.getValue()); // 42
897
+ const value = result.getValue(); // number | undefined
898
+ console.log(value); // 42
877
899
  } else {
878
- console.error(result.getError());
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
- function createUser(email: string): Result<User, string> {
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
- error instanceof Error ? error.message : 'Unknown error',
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.isSuccess) {
1638
+ if (result.isSuccessResult()) {
1475
1639
  const user = result.getValue();
1476
- // Use user
1477
- } else {
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
- // Handle error
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