npm package: @ivandt/json-rules
Description: Rule parsing engine for JSON rules
Author: Milad Hi
Latest version: 2.2.0

[![npm version](https://badge.fury.io/js/@ivandt/json-rules.svg)](https://badge.fury.io/js/@ivandt/json-rules)

| Statements                                                                  | Functions                                                                  | Lines                                                                  |
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| ![Statements](https://img.shields.io/badge/Coverage-97%25-brightgreen.svg) | ![Functions](https://img.shields.io/badge/Coverage-94%25-green.svg) | ![Lines](https://img.shields.io/badge/Coverage-97%25-brightgreen.svg) |

<div align="center">

# JsonRules Engine

**A rule engine for JavaScript & TypeScript**

*Define business logic with JSON rules*

[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
[![npm](https://img.shields.io/npm/v/@ivandt/json-rules.svg)](https://www.npmjs.com/package/@ivandt/json-rules)

</div>

---

## Overview

JsonRules is a rule engine that allows you to define business logic as JSON and evaluate it against data. It provides a simple way to externalize decision-making logic from your application code.

### Use Cases

- **E-commerce**: Pricing rules, discount eligibility, shipping calculations
- **User Management**: Access control, feature flags, user segmentation  
- **Content**: Filtering, moderation, recommendations
- **Workflows**: Approval processes, notifications
- **Configuration**: A/B testing, feature rollouts

### Features

- Type-safe with full TypeScript support
- 40+ built-in operators for common operations
- Support for complex nested conditions
- Template variables for dynamic rules
- Validation for rule structure and syntax
- Works in Node.js and browsers

---

## Installation

```bash
npm install @ivandt/json-rules
```

### Dependencies

This library has one dependency:
- `validator` - Used for advanced validation operators (email, URL, phone numbers, etc.)

---

## Quick Start

```typescript
import { JsonRules, Rule } from "@ivandt/json-rules";

// Define a rule
const rule: Rule = {
  conditions: {
    all: [
      { field: "age", operator: "is greater than or equal", value: 18 },
      { field: "country", operator: "is equal", value: "US" }
    ]
  }
};

// Evaluate against data
const user = { age: 25, country: "US" };
const result = JsonRules.evaluate(rule, user);
console.log(result); // true
```

---

## Operators

JsonRules provides operators for common data operations:

### Equality & Comparison

| Operator | Description | Accepts | Example |
|----------|-------------|---------|---------|
| `is equal` | Equal to | `string \| number \| boolean \| Date \| null` | `{ field: "status", operator: "is equal", value: "active" }` |
| `is not equal` | Not equal to | `string \| number \| boolean \| Date \| null` | `{ field: "status", operator: "is not equal", value: "banned" }` |
| `is greater than` | Greater than comparison | `string \| number \| Date` | `{ field: "age", operator: "is greater than", value: 18 }` |
| `is less than` | Less than comparison | `string \| number \| Date` | `{ field: "price", operator: "is less than", value: 100 }` |
| `is greater than or equal` | Greater than or equal | `string \| number \| Date` | `{ field: "score", operator: "is greater than or equal", value: 80 }` |
| `is less than or equal` | Less than or equal | `string \| number \| Date` | `{ field: "items", operator: "is less than or equal", value: 10 }` |

### Range & Between

| Operator | Description | Accepts | Example |
|----------|-------------|---------|---------|
| `is between numbers` | Number within range (inclusive) | `[number, number]` | `{ field: "age", operator: "is between numbers", value: [18, 65] }` |
| `is not between numbers` | Number outside range | `[number, number]` | `{ field: "temperature", operator: "is not between numbers", value: [32, 100] }` |
| `is between dates` | Date within range (inclusive) | `[Date, Date]` | `{ field: "eventDate", operator: "is between dates", value: [startDate, endDate] }` |
| `is not between dates` | Date outside range | `[Date, Date]` | `{ field: "blackoutDate", operator: "is not between dates", value: [holiday1, holiday2] }` |

### Collection & Array

| Operator | Description | Accepts | Example |
|----------|-------------|---------|---------|
| `in` | Value exists in array | `(string \| number \| boolean \| object \| null)[]` | `{ field: "country", operator: "in", value: ["US", "CA", "UK"] }` |
| `not in` | Value not in array | `(string \| number \| boolean \| object \| null)[]` | `{ field: "status", operator: "not in", value: ["banned", "suspended"] }` |
| `array contains` | Array field contains value | `string \| number \| boolean \| object \| null` | `{ field: "skills", operator: "array contains", value: "javascript" }` |
| `array no contains` | Array field doesn't contain value | `string \| number \| boolean \| object \| null` | `{ field: "permissions", operator: "array no contains", value: "admin" }` |

### String Operations

| Operator | Description | Accepts | Example |
|----------|-------------|---------|---------|
| `contains` | String contains substring | `string` | `{ field: "email", operator: "contains", value: "@company.com" }` |
| `not contains` | String doesn't contain substring | `string` | `{ field: "message", operator: "not contains", value: "spam" }` |
| `contains any` | String contains any substring | `string[]` | `{ field: "title", operator: "contains any", value: ["urgent", "critical"] }` |
| `not contains any` | String contains none of substrings | `string[]` | `{ field: "content", operator: "not contains any", value: ["spam", "scam"] }` |
| `starts with` | String starts with prefix | `string` | `{ field: "productCode", operator: "starts with", value: "PRD-" }` |
| `ends with` | String ends with suffix | `string` | `{ field: "filename", operator: "ends with", value: ".pdf" }` |

### Pattern Matching

| Operator | Description | Accepts | Example |
|----------|-------------|---------|---------|
| `matches` | Matches regex pattern | `{ regex: string, flags?: string }` | `{ field: "email", operator: "matches", value: { regex: "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$" } }` |
| `not matches` | Doesn't match regex pattern | `{ regex: string, flags?: string }` | `{ field: "username", operator: "not matches", value: { regex: "^(admin\|root)$", flags: "i" } }` |

### Date Operations

| Operator | Description | Accepts | Example |
|----------|-------------|---------|---------|
| `is before` | Date is before specified date | `string \| number \| Date` | `{ field: "expiry", operator: "is before", value: new Date('2024-12-31') }` |
| `is after` | Date is after specified date | `string \| number \| Date` | `{ field: "startDate", operator: "is after", value: new Date('2024-01-01') }` |
| `is on or before` | Date is on or before specified date | `string \| number \| Date` | `{ field: "deadline", operator: "is on or before", value: new Date() }` |
| `is on or after` | Date is on or after specified date | `string \| number \| Date` | `{ field: "validFrom", operator: "is on or after", value: new Date() }` |

### Math & Number Validation

| Operator | Description | Accepts | Example |
|----------|-------------|---------|---------|
| `is even` | Number is even | none | `{ field: "quantity", operator: "is even" }` |
| `is odd` | Number is odd | none | `{ field: "productId", operator: "is odd" }` |
| `is positive` | Number is positive (> 0) | none | `{ field: "balance", operator: "is positive" }` |
| `is negative` | Number is negative (< 0) | none | `{ field: "adjustment", operator: "is negative" }` |
| `is empty` | Value is null, undefined, or empty string/array | none | `{ field: "optionalField", operator: "is empty" }` |
| `is not empty` | Value is not empty | none | `{ field: "requiredField", operator: "is not empty" }` |

### Data Validation

| Operator | Description | Accepts | Example |
|----------|-------------|---------|---------|
| `is valid email` | Valid email address | `EmailValidationConfig` (optional) | `{ field: "email", operator: "is valid email" }` |
| `is valid phone` | Valid phone number | `PhoneValidationConfig` | `{ field: "phone", operator: "is valid phone", value: { locale: "us" } }` |
| `is URL` | Valid URL | `URLValidationConfig` | `{ field: "website", operator: "is URL", value: { requireTld: false } }` |
| `is UUID` | Valid UUID | `UUIDValidationConfig` | `{ field: "id", operator: "is UUID", value: { version: 4 } }` |
| `is EAN` | Valid EAN barcode | none | `{ field: "barcode", operator: "is EAN" }` |
| `is IMEI` | Valid IMEI number | `IMEIValidationConfig` | `{ field: "deviceId", operator: "is IMEI", value: { allowHyphens: true } }` |
| `is unit` | Valid unit of measurement | `UnitType` | `{ field: "distance", operator: "is unit", value: "length" }` |
| `is country` | Valid country identifier | `CountryValidationConfig` | `{ field: "country", operator: "is country", value: { format: "iso2" } }` |
| `is domain` | Valid domain name | `DomainValidationConfig` | `{ field: "domain", operator: "is domain", value: { requireTld: true } }` |

---

## Rule Structure

### Basic Rule

```typescript
interface Rule {
  conditions: Condition | Condition[];
  default?: any;
}
```

### Conditions

Rules support three logical operators:

| Type | Logic | Description |
|------|-------|-------------|
| `all` | **AND** | All constraints must be true |
| `any` | **OR** | At least one constraint must be true |
| `none` | **NOT** | No constraints should be true |

### Constraints

```typescript
{
  field: string,           // Property path (supports dot notation)
  operator: string,        // Comparison operator
  value: any              // Expected value or template reference
}
```

---

## Examples

### Basic Example

```typescript
const rule: Rule = {
  conditions: {
    all: [
      { field: "age", operator: "is greater than or equal", value: 21 },
      { field: "country", operator: "in", value: ["US", "CA"] }
    ]
  }
};

const user = { age: 25, country: "US" };
const result = JsonRules.evaluate(rule, user); // true
```

### Complex Conditions

```typescript
const complexRule: Rule = {
  conditions: {
    any: [
      {
        all: [
          { field: "membershipTier", operator: "is equal", value: "premium" },
          { field: "accountAge", operator: "is greater than", value: 365 }
        ]
      },
      {
        all: [
          { field: "totalSpent", operator: "is greater than", value: 1000 },
          { field: "lastPurchase", operator: "is after", value: new Date('2024-01-01') }
        ]
      }
    ]
  }
};
```

### Validation Operators

```typescript
const validationRule: Rule = {
  conditions: {
    all: [
      { field: "email", operator: "is valid email", value: null },
      { field: "phone", operator: "is valid phone", value: { locale: "us" } },
      { field: "website", operator: "is URL", value: { protocols: ["https"] } }
    ]
  }
};
```

### Template Variables

```typescript
const dynamicRule: Rule = {
  conditions: {
    all: [
      { field: "endDate", operator: "is after", value: "{startDate}" },
      { field: "price", operator: "is less than", value: "{maxBudget}" }
    ]
  }
};
```

---

## API Reference

### JsonRules.evaluate()

```typescript
static evaluate<T>(
  rule: Rule, 
  criteria: object | object[], 
  trustRule?: boolean
): T | boolean
```

### JsonRules.validate()

```typescript
static validate(rule: Rule): ValidationResult
```

### ValidationResult

```typescript
interface ValidationResult {
  isValid: boolean;
  error?: ValidationError;
}
```

---

## Phone Number Validation

To use phone validation, import the specific locale validators:

```typescript
// Import specific locales
import "@ivandt/json-rules/validators/phone/us";
import "@ivandt/json-rules/validators/phone/gb";
import "@ivandt/json-rules/validators/phone/de";

const rule: Rule = {
  conditions: {
    all: [
      { field: "phone", operator: "is valid phone", value: { locale: "us" } }
    ]
  }
};
```

---

## Contributing

1. Fork the repository
2. Create a feature branch: `git checkout -b feature/new-feature`
3. Make your changes with tests
4. Run the test suite: `npm test`
5. Submit a pull request

---

## License

MIT License - see [LICENSE](LICENSE) file for details.