npm package: @kypython/design-by-contract
Description: A Design by Contract library for Node.js/TypeScript enforcing pre-conditions and post-conditions
Latest version: 1.0.0

# Design by Contract

A lightweight TypeScript library for enforcing **Design by Contract** principles in Node.js applications. Based on the methodology described in "The Pragmatic Programmer" by Andrew Hunt and David Thomas.

## What is Design by Contract?

Design by Contract (DbC) is a programming methodology that formalizes the relationship between a function and its callers through **contracts**. These contracts specify:

- **Pre-conditions**: What must be true before a function is called (caller's obligations)
- **Post-conditions**: What will be true after a function returns (function's obligations)

This approach makes the assumptions and guarantees of your code explicit, leading to:
- Better error messages when contracts are violated
- Clearer documentation of function requirements
- Earlier detection of bugs
- Improved code reliability

## Installation

```bash
npm install @kypython/design-by-contract
```

## Quick Start

```typescript
import { contract } from '@kypython/design-by-contract';

const safeDivide = contract({
  pre: (a, b) => b !== 0,
  preMessage: 'Divisor must not be zero',
  fn: (a: number, b: number) => a / b,
  post: (result) => Number.isFinite(result),
  postMessage: 'Result must be a finite number',
});

safeDivide(10, 2); // ✅ Returns 5
safeDivide(10, 0); // ❌ Throws ContractViolationError: precondition
```

**Note:** Due to TypeScript's reserved `require` identifier in CommonJS modules, the pre-condition function is named `precondition`. You can import it with your preferred name using:
```typescript
import { precondition as require } from '@kypython/design-by-contract';
```

## API Reference

### `contract(config)`

Wraps a function with contract enforcement. The wrapped function will enforce pre-conditions before execution and post-conditions after execution.

**Parameters:**
- `config.pre` (optional): A predicate function that receives the function arguments and returns `true` if the pre-condition is met
- `config.preMessage` (optional): Custom error message for pre-condition failures (default: "Pre-condition failed")
- `config.fn`: The function to wrap with contract enforcement
- `config.post` (optional): A predicate function that receives the function result and returns `true` if the post-condition is met
- `config.postMessage` (optional): Custom error message for post-condition failures (default: "Post-condition failed")

**Returns:** The wrapped function with contract enforcement

**Example:**

```typescript
const getFirst = contract({
  pre: (arr: unknown[]) => arr.length > 0,
  preMessage: 'Array must not be empty',
  fn: <T>(arr: T[]): T => arr[0],
  post: (result) => result !== undefined,
  postMessage: 'Result must not be undefined',
});
```

### `precondition(condition, message)` (or alias as `require`)

Enforces a pre-condition. Throws `ContractViolationError` if the condition is false.

**Note:** Named `precondition` instead of `require` to avoid conflict with TypeScript's reserved `require` identifier. You can alias it:
```typescript
import { precondition as require } from '@kypython/design-by-contract';
```

**Parameters:**
- `condition`: The condition that must be true
- `message`: Error message to display if condition fails

**Example:**

```typescript
import { precondition as require } from '@kypython/design-by-contract';

function divide(a: number, b: number): number {
  require(b !== 0, "Divisor must not be zero");
  return a / b;
}
```

### `ensure(condition, message)`

Enforces a post-condition. Throws `ContractViolationError` if the condition is false.

**Parameters:**
- `condition`: The condition that must be true
- `message`: Error message to display if condition fails

**Example:**

```typescript
import { ensure } from 'design-by-contract';

function divide(a: number, b: number): number {
  require(b !== 0, "Divisor must not be zero");
  const result = a / b;
  ensure(Number.isFinite(result), "Result must be a finite number");
  return result;
}
```

### `ContractViolationError`

Custom error class thrown when contracts fail. It extends the standard `Error` class and includes:

- `type`: Either `'precondition'` or `'postcondition'`
- `message`: The error message provided

**Example:**

```typescript
import { ContractViolationError } from 'design-by-contract';

try {
  safeDivide(10, 0);
} catch (error) {
  if (error instanceof ContractViolationError) {
    console.log(error.type); // 'precondition'
    console.log(error.message); // 'Contract violation (precondition): ...'
  }
}
```

## Examples

### Example 1: Safe Division

```typescript
import { contract } from '@kypython/design-by-contract';

const safeDivide = contract({
  pre: (a: number, b: number) => b !== 0,
  preMessage: 'Divisor must not be zero',
  fn: (a: number, b: number) => a / b,
  post: (result: number) => Number.isFinite(result),
  postMessage: 'Result must be a finite number',
});
```

### Example 2: Array Operations

```typescript
import { contract } from '@kypython/design-by-contract';

const createRange = contract({
  pre: (start: number, end: number) => start <= end,
  preMessage: 'Start must be less than or equal to end',
  fn: (start: number, end: number): number[] => {
    const result: number[] = [];
    for (let i = start; i <= end; i++) {
      result.push(i);
    }
    return result;
  },
  post: (result: number[]) => result.length > 0,
  postMessage: 'Range must contain at least one element',
});
```

### Example 3: User Validation with Manual Assertions

```typescript
import { precondition as require, ensure } from 'design-by-contract';

interface User {
  name: string;
  age: number;
  email: string;
}

function createUser(name: string, age: number, email: string): User {
  // Pre-conditions
  require(name.trim().length > 0, 'Name must not be empty');
  require(age >= 0 && age <= 150, 'Age must be between 0 and 150');
  require(email.includes('@'), 'Email must contain @ symbol');

  const user: User = {
    name: name.trim(),
    age,
    email: email.toLowerCase(),
  };

  // Post-conditions
  ensure(user.name.length > 0, 'User name must not be empty after creation');
  ensure(user.email.includes('@'), 'User email must be valid');

  return user;
}
```

## Usage Patterns

### Pattern 1: Contract Wrapper

Use when you want declarative contract definition around a function:

```typescript
const myFunction = contract({
  pre: (arg1, arg2) => /* condition */,
  fn: (arg1, arg2) => { /* implementation */ },
  post: (result) => /* condition */,
});
```

### Pattern 2: Manual Assertions

Use when you need more control or want to check conditions inside the function body:

```typescript
import { precondition as require, ensure } from 'design-by-contract';

function myFunction(arg1: Type1, arg2: Type2): ReturnType {
  require(/* condition */, 'Pre-condition message');
  // ... implementation ...
  ensure(/* condition */, 'Post-condition message');
  return result;
}
```

### Pattern 3: Mixed Approach

Combine both patterns for maximum flexibility:

```typescript
const myFunction = contract({
  pre: (arg) => /* basic validation */,
  fn: (arg) => {
    require(/* additional validation */, 'More specific message');
    // ... implementation ...
  },
  post: (result) => /* validation */,
});
```

## Testing

```bash
npm test
```

The test suite includes:
- Unit tests for `require()` and `ensure()`
- Unit tests for the `contract()` wrapper
- Integration tests with real-world scenarios

## TypeScript Support

This library is written in TypeScript and provides full type safety. The `contract()` function preserves the original function's type signature, so you get full IntelliSense and type checking.

## Best Practices

1. **Use clear, descriptive messages**: Your error messages should help developers understand what went wrong and why.

2. **Pre-conditions check input validity**: Pre-conditions should validate that the caller has met their obligations (e.g., passed valid arguments).

3. **Post-conditions check output validity**: Post-conditions should validate that your function has met its obligations (e.g., returned a valid result).

4. **Don't use contracts for business logic**: Contracts are for validation, not for implementing your application's core functionality.

5. **Consider performance**: Contract checks add overhead. Consider using them primarily in development/testing environments if performance is critical.

## References

- [The Pragmatic Programmer](https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/) by Andrew Hunt and David Thomas
- [Design by Contract](https://en.wikipedia.org/wiki/Design_by_contract) on Wikipedia
- [Eiffel Programming Language](https://www.eiffel.org/) - The original language featuring Design by Contract

## License

MIT

## Contributing

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