UNPKG

@ivandt/json-rules

Version:

Rule parsing engine for JSON rules

336 lines (251 loc) 12.5 kB
[![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 = await 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 = await 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 async evaluate<T>( rule: Rule, criteria: object | object[], trustRule?: boolean ): Promise<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.