npm package: @rolandsall24/specification-pattern
Description: A TypeScript implementation of the Specification Pattern for Domain-Driven Design, enabling composable business rules with type safety
Author: Roland Salloum
Latest version: 0.1.1

# Specification Pattern

A TypeScript implementation of the Specification Pattern for Domain-Driven Design, enabling composable business rules with type safety.

## Features

- Clean implementation of the DDD Specification Pattern
- Type-safe specification composition with TypeScript generics
- Logical operators (AND, OR, NOT) for combining specifications
- Zero runtime dependencies
- Framework-agnostic design
- Comprehensive error messages for debugging
- Easy to extend and customize

## Installation

```bash
npm install @rolandsall24/specification-pattern
```

## What is the Specification Pattern?

The Specification Pattern is a Domain-Driven Design pattern that encapsulates business rules into reusable, combinable objects. It allows you to:

- Express complex business rules in a clear, maintainable way
- Combine simple specifications into complex ones using logical operators
- Keep your domain logic separate from infrastructure concerns
- Make business rules testable and reusable

## Quick Start

### 1. Create a Specification

Extend `CompositeSpecification` and implement the required methods:

```typescript
import { CompositeSpecification } from '@rolandsall24/specification-pattern';

interface User {
  age: number;
  isActive: boolean;
  email: string;
}

class IsAdultSpecification extends CompositeSpecification<User> {
  isSatisfiedBy(user: User): boolean {
    return user.age >= 18;
  }

  getErrorMessage(): string {
    return 'User must be at least 18 years old';
  }
}

class IsActiveUserSpecification extends CompositeSpecification<User> {
  isSatisfiedBy(user: User): boolean {
    return user.isActive;
  }

  getErrorMessage(): string {
    return 'User must be active';
  }
}
```

### 2. Use Specifications

```typescript
const user = {
  age: 25,
  isActive: true,
  email: 'john@example.com'
};

const isAdult = new IsAdultSpecification();
const isActive = new IsActiveUserSpecification();

// Check individual specifications
if (isAdult.isSatisfiedBy(user)) {
  console.log('User is an adult');
}

// Combine specifications using AND
const canPurchase = isAdult.and(isActive);
if (canPurchase.isSatisfiedBy(user)) {
  console.log('User can make purchases');
} else {
  console.log(canPurchase.getErrorMessage());
}
```

### 3. Combine Specifications

Specifications can be combined using logical operators:

```typescript
// AND: Both must be satisfied
const adultAndActive = isAdult.and(isActive);

// OR: At least one must be satisfied
const adultOrActive = isAdult.or(isActive);

// NOT: Must not be satisfied
const notAdult = isAdult.not();

// Complex combinations
const eligibleUser = isAdult.and(isActive).and(hasVerifiedEmail);
```

## Advanced Usage

### Domain Validation Example

```typescript
import { CompositeSpecification } from '@rolandsall24/specification-pattern';

interface Order {
  total: number;
  items: number;
  customerId: string;
  isPaid: boolean;
}

class MinimumOrderValueSpecification extends CompositeSpecification<Order> {
  constructor(private minValue: number) {
    super();
  }

  isSatisfiedBy(order: Order): boolean {
    return order.total >= this.minValue;
  }

  getErrorMessage(): string {
    return `Order total must be at least $${this.minValue}`;
  }
}

class HasItemsSpecification extends CompositeSpecification<Order> {
  isSatisfiedBy(order: Order): boolean {
    return order.items > 0;
  }

  getErrorMessage(): string {
    return 'Order must contain at least one item';
  }
}

class IsPaidSpecification extends CompositeSpecification<Order> {
  isSatisfiedBy(order: Order): boolean {
    return order.isPaid;
  }

  getErrorMessage(): string {
    return 'Order must be paid';
  }
}

// Usage
const order = {
  total: 150,
  items: 3,
  customerId: 'cust-123',
  isPaid: true
};

const canShipOrder = new MinimumOrderValueSpecification(50)
  .and(new HasItemsSpecification())
  .and(new IsPaidSpecification());

if (canShipOrder.isSatisfiedBy(order)) {
  console.log('Order can be shipped');
} else {
  console.log('Cannot ship order:', canShipOrder.getErrorMessage());
}
```

### Parameterized Specifications

Create reusable specifications with parameters:

```typescript
class MinimumAgeSpecification extends CompositeSpecification<User> {
  constructor(private minimumAge: number) {
    super();
  }

  isSatisfiedBy(user: User): boolean {
    return user.age >= this.minimumAge;
  }

  getErrorMessage(): string {
    return `User must be at least ${this.minimumAge} years old`;
  }
}

// Create different age requirements
const canDrink = new MinimumAgeSpecification(21);
const canVote = new MinimumAgeSpecification(18);
const canRetire = new MinimumAgeSpecification(65);
```

### Business Rules Encapsulation

```typescript
interface Product {
  price: number;
  stock: number;
  isActive: boolean;
  category: string;
}

class IsInStockSpecification extends CompositeSpecification<Product> {
  isSatisfiedBy(product: Product): boolean {
    return product.stock > 0;
  }

  getErrorMessage(): string {
    return 'Product is out of stock';
  }
}

class IsActiveProductSpecification extends CompositeSpecification<Product> {
  isSatisfiedBy(product: Product): boolean {
    return product.isActive;
  }

  getErrorMessage(): string {
    return 'Product is not active';
  }
}

class IsPremiumProductSpecification extends CompositeSpecification<Product> {
  isSatisfiedBy(product: Product): boolean {
    return product.price >= 100;
  }

  getErrorMessage(): string {
    return 'Product must be a premium product (price >= $100)';
  }
}

// Compose complex business rules
const canBePurchased = new IsInStockSpecification()
  .and(new IsActiveProductSpecification());

const qualifiesForFreeShipping = new IsPremiumProductSpecification()
  .and(new IsInStockSpecification());
```

### Filtering Collections

```typescript
const users: User[] = [
  { age: 17, isActive: true, email: 'teen@example.com' },
  { age: 25, isActive: true, email: 'adult@example.com' },
  { age: 30, isActive: false, email: 'inactive@example.com' },
  { age: 45, isActive: true, email: 'senior@example.com' }
];

const activeAdults = new IsAdultSpecification()
  .and(new IsActiveUserSpecification());

const eligibleUsers = users.filter(user => activeAdults.isSatisfiedBy(user));
console.log(eligibleUsers);
// Output: Users aged >= 18 and active
```

## API Reference

### Interfaces

#### `ISpecification<T>`

The base specification interface.

```typescript
interface ISpecification<T> {
  isSatisfiedBy(candidate: T): boolean;
  and(other: ISpecification<T>): ISpecification<T>;
  or(other: ISpecification<T>): ISpecification<T>;
  not(): ISpecification<T>;
  getErrorMessage(): string;
}
```

### Classes

#### `CompositeSpecification<T>`

Abstract base class for implementing specifications.

**Methods:**

- `abstract isSatisfiedBy(candidate: T): boolean` - Checks if the candidate satisfies this specification
- `abstract getErrorMessage(): string` - Returns the error message when not satisfied
- `and(other: ISpecification<T>): ISpecification<T>` - Combines with another specification using AND logic
- `or(other: ISpecification<T>): ISpecification<T>` - Combines with another specification using OR logic
- `not(): ISpecification<T>` - Negates this specification

## Design Patterns & Best Practices

### 1. Single Responsibility Principle

Each specification should encapsulate one business rule:

```typescript
// Good: Each specification has one responsibility
class IsAdultSpecification extends CompositeSpecification<User> { ... }
class IsActiveSpecification extends CompositeSpecification<User> { ... }
const eligibleUser = new IsAdultSpecification().and(new IsActiveSpecification());

// Bad: Specification does too much
class IsEligibleUserSpecification extends CompositeSpecification<User> {
  isSatisfiedBy(user: User): boolean {
    return user.age >= 18 && user.isActive && user.hasVerifiedEmail;
  }
}
```

### 2. Composition Over Inheritance

Combine simple specifications rather than creating complex hierarchies:

```typescript
// Good: Compose simple specifications
const premiumEligible = new IsAdultSpecification()
  .and(new HasPremiumAccountSpecification())
  .and(new HasValidPaymentMethodSpecification());

// Bad: Deep inheritance
class PremiumEligibleUserSpecification extends IsAdultSpecification { ... }
```

### 3. Immutability

Specifications should be immutable and return new instances:

```typescript
const spec1 = new IsAdultSpecification();
const spec2 = spec1.and(new IsActiveSpecification());
// spec1 remains unchanged, spec2 is a new specification
```

### 4. Clear Error Messages

Provide descriptive error messages for debugging:

```typescript
class MinimumBalanceSpecification extends CompositeSpecification<Account> {
  constructor(private minBalance: number) {
    super();
  }

  isSatisfiedBy(account: Account): boolean {
    return account.balance >= this.minBalance;
  }

  getErrorMessage(): string {
    return `Account balance must be at least $${this.minBalance}`;
  }
}
```

## Use Cases

The Specification Pattern is particularly useful for:

1. **Domain Validation**: Validating entities against business rules
2. **Querying**: Building complex queries in a type-safe manner
3. **Business Rules**: Encapsulating business logic for reuse
4. **Access Control**: Defining permission rules
5. **Filtering**: Filtering collections based on criteria
6. **Workflow Rules**: Defining state transition rules

## Comparison with Other Patterns

### Specification vs Strategy Pattern

- **Strategy**: Encapsulates algorithms/behaviors
- **Specification**: Encapsulates business rules and supports composition

### Specification vs Validator

- **Validator**: Typically validates all rules at once
- **Specification**: Allows selective, composable rule checking

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT

## Author

Roland Salloum

## Related Patterns

- Domain-Driven Design (DDD)
- Composite Pattern
- Chain of Responsibility Pattern
- Strategy Pattern

## Resources

- [Specification Pattern - Martin Fowler](https://www.martinfowler.com/apsupp/spec.pdf)
- [Domain-Driven Design by Eric Evans](https://www.domainlanguage.com/ddd/)