npm package: @libs-for-dev/nestjs-ddd-library
Description: NestJS DDD library
Author: libs-for-dev
Latest version: 1.4.0

# NestJS DDD Library

A comprehensive library providing Domain-Driven Design (DDD) building blocks for NestJS applications. This library helps you implement DDD patterns such as Value Objects, Entities, and Aggregates in your NestJS projects with TypeScript.

## Features

- **Entities**: Objects that have a distinct identity that runs through time and different states
- **Aggregates**: Cluster of domain objects that can be treated as a single unit
- **Result types**: Using oxide.ts for type-safe error handling
- **Value Objects**: Immutable objects that contain attributes but have no conceptual identity with examples:
  - **UUID**: Built-in UUID v7 generation and validation
  - **Email**: Email value object with validation
  - **Money**: Money value object with amount and currency (ISO 4217) validation
  - **PhoneNumber**: Phone number value object with mobile phone validation
  - **Url**: URL value object with protocol validation

## Installation

```bash
# Using npm
npm install @libs-for-dev/nestjs-ddd-library

# Using yarn
yarn add @libs-for-dev/nestjs-ddd-library

# Using pnpm
pnpm add @libs-for-dev/nestjs-ddd-library
```

### Peer Dependencies

This library requires the following peer dependencies:

```bash
yarn add @nestjs/cqrs class-validator fast-equals oxide.ts ui7
```

## Usage Examples

### Value Objects

#### UUID

```typescript
import { Uuid } from '@libs-for-dev/nestjs-ddd-library';

// Generate a new UUID (v7)
const uuid = Uuid.generate();
console.log(uuid.value); // Generated UUID v7 string

// Create from existing UUID string
const uuidResult = Uuid.create('de49eb8d-9199-4046-a784-d015148f95a7');

if (uuidResult.isOk()) {
  const uuid = uuidResult.unwrap();
  console.log(uuid.value); // de49eb8d-9199-4046-a784-d015148f95a7
} else {
  // Handle error
  console.error(uuidResult.unwrapErr());
}
```

#### Email

```typescript
import { Email } from '@libs-for-dev/nestjs-ddd-library';

// Create an Email value object
const emailResult = Email.create('user@example.com');

if (emailResult.isOk()) {
  const email = emailResult.unwrap();
  console.log(email.value); // user@example.com
} else {
  // Handle error
  console.error(emailResult.unwrapErr());
}
```

#### Money

```typescript
import { Money } from '@libs-for-dev/nestjs-ddd-library';

// Create a Money value object
const moneyResult = Money.create(100.50, 'USD');

if (moneyResult.isOk()) {
  const money = moneyResult.unwrap();
  console.log(money.amount); // 100.50
  console.log(money.currency); // USD
  console.log(money.value); // { amount: 100.50, currency: 'USD' }
} else {
  // Handle error
  console.error(moneyResult.unwrapErr());
}
```

#### PhoneNumber

```typescript
import { PhoneNumber } from '@libs-for-dev/nestjs-ddd-library';

// Create a PhoneNumber value object
const phoneResult = PhoneNumber.create('+1234567890');

if (phoneResult.isOk()) {
  const phone = phoneResult.unwrap();
  console.log(phone.value); // +1234567890
} else {
  // Handle error
  console.error(phoneResult.unwrapErr());
}
```

#### Url

```typescript
import { Url } from '@libs-for-dev/nestjs-ddd-library';

// Create a Url value object
const urlResult = Url.create('https://example.com');

if (urlResult.isOk()) {
  const url = urlResult.unwrap();
  console.log(url.value); // https://example.com
} else {
  // Handle error
  console.error(urlResult.unwrapErr());
}
```

### Entities

`AbstractEntity` provides immutable properties through `DeepReadonly`. All properties, including nested objects, are readonly and cannot be modified directly. Changes must be made through entity domain methods.

#### Basic Usage

```typescript
import { AbstractEntity, Uuid } from '@libs-for-dev/nestjs-ddd-library';

// Define entity properties
interface UserProps {
  email: string;
  name: string;
}

// Create an entity class
class User extends AbstractEntity<Uuid, UserProps> {
  public static create(id: Uuid, props: UserProps): User {
    return new User(id, props);
  }

  // Add domain methods here
  public updateName(name: string): void {
    this.propsData = {
      props: {
        ...this.props,
        name
      }
    };
  }
}

// Usage
const userId = Uuid.generate();
const user = User.create(userId, {
  email: 'user@example.com',
  name: 'John Doe'
});

// Access properties
console.log(user.id.value); // UUID
console.log(user.props.name); // John Doe

// Update properties using domain methods
user.updateName('Jane Doe');
console.log(user.props.name); // Jane Doe
```

#### Readonly Properties with Nested Objects

Properties returned by `props` getter are deeply readonly, including nested objects. This ensures immutability and prevents accidental modifications:

```typescript
import { AbstractEntity, Uuid } from '@libs-for-dev/nestjs-ddd-library';

interface UserProfileProps {
  personal: {
    firstName: string;
    lastName: string;
  };
  settings: {
    theme: string;
  };
}

class UserProfile extends AbstractEntity<Uuid, UserProfileProps> {
  public static create(id: Uuid, props: UserProfileProps): UserProfile {
    return new UserProfile(id, props);
  }

  public updateFirstName(firstName: string): void {
    this.propsData = {
      props: {
        ...this.props,
        personal: {
          ...this.props.personal,
          firstName
        }
      }
    };
  }
}

// Usage
const profile = UserProfile.create(Uuid.generate(), {
  personal: {
    firstName: 'John',
    lastName: 'Doe'
  },
  settings: {
    theme: 'dark'
  }
});

// All properties are readonly, including nested objects
// This will throw an error at runtime:
// profile.props.personal.firstName = 'Jane'; // ❌ Error: Cannot assign to read-only property

// This will also throw an error:
// profile.props.settings.theme = 'light'; // ❌ Error: Cannot assign to read-only property

// Changes must be made through domain methods
profile.updateFirstName('Jane');
console.log(profile.props.personal.firstName); // Jane

// Previous references remain unchanged (immutability)
const oldProps = profile.props;
profile.updateFirstName('Bob');
console.log(oldProps.personal.firstName); // Jane (unchanged)
console.log(profile.props.personal.firstName); // Bob (updated)
```

## Domain Events

The library integrates with NestJS CQRS to support domain events:

```typescript
import { AbstractEntity, Uuid } from '@libs-for-dev/nestjs-ddd-library';
import { IEvent } from '@nestjs/cqrs';

// Define a domain event
class UserCreatedEvent implements IEvent {
  constructor(public readonly userId: string, public readonly email: string) {}
}

// Entity with domain events
class User extends AbstractEntity<Uuid, UserProps> {
  public static create(id: Uuid, props: UserProps): User {
    const user = new User(id, props);
    user.apply(new UserCreatedEvent(id.value, props.email));
    return user;
  }
}

// In your NestJS application
@EventsHandler(UserCreatedEvent)
export class UserCreatedHandler implements IEventHandler<UserCreatedEvent> {
  handle(event: UserCreatedEvent) {
    console.log(`User created: ${event.userId} with email ${event.email}`);
  }
}
```

## License

MIT