@ivandt/json-rules
Version:
Rule parsing engine for JSON rules
336 lines (251 loc) • 12.5 kB
Markdown
[](https://badge.fury.io/js/@ivandt/json-rules)
| Statements | Functions | Lines |
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|  |  |  |
<div align="center">
# JsonRules Engine
**A rule engine for JavaScript & TypeScript**
*Define business logic with JSON rules*
[](https://www.typescriptlang.org/)
[](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.