@vpwhite/vue-rule-builder
Version:
A powerful, flexible Vue 3 component library for building complex business rules with an intuitive visual interface. Transform your business logic into interactive, maintainable rule definitions.
1,387 lines (1,106 loc) โข 35.5 kB
Markdown
# Vue Rule Builder
A powerful, flexible Vue 3 component library for building complex business rules with an intuitive visual interface. Transform your business logic into interactive, maintainable rule definitions.
## ๐ Features
- ๐ฏ **Visual Rule Builder**: Drag-and-drop interface for creating complex business rules
- ๐ง **Multiple Rule Types**: Support for property rules, expressions, and reusable rule references
- ๐จ **Modern UI**: Clean, responsive design with smooth animations and dark mode support
- ๐ **Real-time Validation**: Instant feedback on rule validity with detailed error messages
- ๐งช **Test Panel**: Test rules against sample data with live preview
- ๐พ **Auto-save**: Automatic local backups with history management
- ๐ **Plugin Architecture**: Easy integration with any backend API
- ๐ฑ **Mobile Responsive**: Works seamlessly on desktop, tablet, and mobile devices
- ๐ **TypeScript**: Full TypeScript support with comprehensive type definitions
- ๐๏ธ **Highly Customizable**: Extensive theming and configuration options
## ๐ฆ Installation
```bash
npm install vue-rule-builder
```
### Peer Dependencies
```bash
npm install vue@^3.5.0 pinia@^2.3.0
# Optional: for advanced data fetching
npm install @tanstack/vue-query@^5.56.0
```
## ๐๏ธ Architecture Overview
The Vue Rule Builder follows a modular architecture with clear separation of concerns:
```
Vue Rule Builder Architecture
โโโ Core Components
โ โโโ RuleBuilder (Main container)
โ โโโ RuleGroup (Logical groups)
โ โโโ RuleRow (Individual rules)
โ โโโ AddRuleDropdown (Rule creation)
โโโ Supporting Components
โ โโโ ExpressionRow (Custom expressions)
โ โโโ ReferenceRow (Reusable rules)
โ โโโ ValueSelector (Value input)
โ โโโ PropertySelectorInput (Field selection)
โโโ State Management
โ โโโ RuleBuilderStore (Pinia store)
โ โโโ Instance Management (Multi-instance support)
โ โโโ Auto-save System
โโโ Utilities
โ โโโ Preview Generation
โ โโโ Validation Engine
โ โโโ Data Transformation
โโโ Type System
โโโ RuleNodeDto (Core data structure)
โโโ TreeNode (Field definitions)
โโโ ValidationResult (Error handling)
```
## ๐ฏ Core Concepts
### Rule Types
1. **Property Rules**: Compare field values with operators (equals, contains, etc.)
2. **Expression Rules**: Custom JavaScript expressions for complex logic
3. **Reference Rules**: Reusable rule definitions for common patterns
4. **Group Rules**: Logical containers (AND/OR/NOT) for organizing rules
### Data Flow
```
User Interaction โ Component Events โ Store Updates โ State Change โ UI Re-render
โ
Validation Engine โ Error Display โ User Feedback
โ
Auto-save System โ Local Storage โ History Management
```
## ๐ Quick Start
### Basic Usage
```vue
<template>
<div class="app">
<RuleBuilder
v-model:rule="myRule"
:entity-name="'User'"
:data-providers="dataProviders"
@validation-change="handleValidation"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RuleBuilder, useRuleBuilderInstance } from 'vue-rule-builder'
import type { RuleNodeDto, DataProviders } from 'vue-rule-builder'
// Initialize rule state
const myRule = ref<RuleNodeDto>({
id: 'root',
kind: 0, // NodeKind.Group
combinator: 0, // CombinatorKind.And
not: false,
children: []
})
// Data providers for API integration
const dataProviders: DataProviders = {
fetchBaseConfig: async ({ entityName }) => {
const response = await fetch(`/api/${entityName}/config`)
return response.json()
},
fetchTree: async ({ entityName, path }) => {
const url = path
? `/api/${entityName}/tree?path=${encodeURIComponent(path)}`
: `/api/${entityName}/tree`
const response = await fetch(url)
return response.json()
},
testRule: async (rule, testData) => {
const response = await fetch('/api/rules/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rule, testData })
})
return response.json()
}
}
const handleValidation = (result: { valid: boolean; errors: string[] }) => {
console.log('Validation:', result)
}
</script>
```
### With Static Data
```vue
<template>
<RuleBuilder
:rule="myRule"
:tree-data="staticTreeData"
:available-references="ruleReferences"
@update:rule="handleRuleChange"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { RuleBuilder } from 'vue-rule-builder'
import type { RuleNodeDto, TreeNode } from 'vue-rule-builder'
const myRule = ref<RuleNodeDto>({
id: 'root',
kind: 0,
combinator: 0,
not: false,
children: []
})
const staticTreeData: TreeNode[] = [
{
id: 'user',
name: 'User',
type: 'object',
path: 'user',
isExpandable: true,
icon: 'User',
children: [
{
id: 'user.email',
name: 'Email Address',
type: 'string',
path: 'user.email',
isExpandable: false,
icon: 'Mail'
},
{
id: 'user.age',
name: 'Age',
type: 'number',
path: 'user.age',
isExpandable: false,
icon: 'Hash'
}
]
}
]
const ruleReferences = [
{ id: 'ref1', name: 'Active Users', description: 'Users with status = active' },
{ id: 'ref2', name: 'VIP Customers', description: 'Customers with tier = VIP' }
]
</script>
```
## ๐ Complete Component API Reference
### RuleBuilder (Main Component)
The primary component that orchestrates the entire rule building experience.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `rule` | `RuleNodeDto` | `undefined` | Initial rule data structure |
| `instanceId` | `string` | Auto-generated UUID | Unique identifier for this rule builder instance |
| `entityName` | `string` | `undefined` | Entity name for API calls and context |
| `viewName` | `string` | `undefined` | View name for API calls |
| `parentEntityName` | `string` | `undefined` | Parent entity name for nested contexts |
| `availableTabs` | `string[]` | `['properties', 'functions', 'expression']` | Available tabs in property selector modal |
| `dataProviders` | `DataProviders` | `undefined` | API providers for data fetching |
| `treeData` | `TreeNode[]` | `undefined` | Static tree data for field selection |
| `availableReferences` | `Reference[]` | `[]` | Available rule references for reuse |
| `features` | `FeatureFlags` | `{}` | Feature toggles for enabling/disabling functionality |
| `includeMeta` | `boolean` | `false` | Include metadata in v-model output |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `update:modelValue` | `RuleNodeDto \| { tree: RuleNodeDto, meta: RuleMeta }` | Fired when rule changes |
| `update:rule` | `RuleNodeDto` | Fired when rule changes (alias for update:modelValue) |
| `validation-change` | `ValidationResult` | Fired when validation state changes |
| `error` | `Error` | Fired when an error occurs during operation |
| `openDeveloperTools` | `string` | Fired when developer tools are opened (groupId) |
#### Slots
| Slot | Props | Description |
|------|-------|-------------|
| `default` | None | Main content area (rarely used) |
| `toolbar` | `{ rule: RuleNodeDto, instanceId: string }` | Custom toolbar content |
| `footer` | `{ rule: RuleNodeDto, validation: ValidationResult }` | Custom footer content |
#### Example Usage
```vue
<template>
<RuleBuilder
v-model:rule="myRule"
:instance-id="'my-rule-builder'"
:entity-name="'Product'"
:data-providers="providers"
:features="{
enableRules: true,
enableExpressions: true,
enableReferences: true,
enableNotOperator: true,
enableDeveloperTools: true
}"
@validation-change="handleValidation"
@error="handleError"
>
<template #toolbar="{ rule, instanceId }">
<div class="custom-toolbar">
<button @click="exportRule">Export</button>
<button @click="importRule">Import</button>
</div>
</template>
</RuleBuilder>
</template>
```
### RuleGroup Component
Manages logical groups of rules with AND/OR/NOT operators.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `group` | `RuleNodeDto` | Required | Group data structure |
| `isRoot` | `boolean` | `false` | Whether this is the root group |
| `parentId` | `string` | `undefined` | Parent group ID for nested groups |
| `availableReferences` | `Reference[]` | `[]` | Available rule references |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `openDeveloperTools` | `string` | Fired when developer tools are opened |
#### Internal State
- `operatorValue`: Current logical operator (AND/OR)
- `features`: Available features from store
- `expandedNodes`: Set of expanded node IDs
#### Example Usage
```vue
<template>
<RuleGroup
:group="groupData"
:is-root="true"
:available-references="references"
@openDeveloperTools="handleOpenDevTools"
/>
</template>
```
### RuleRow Component
Individual rule component for property-based rules.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `rule` | `RuleNodeDto` | Required | Rule data structure |
| `groupId` | `string` | Required | Parent group ID |
| `entityName` | `string` | `undefined` | Entity name for API calls |
| `treeData` | `TreeNode[]` | `undefined` | Static tree data |
| `treeUrl` | `string` | `undefined` | Tree data API URL |
| `functionsUrl` | `string` | `undefined` | Functions API URL |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `remove` | `void` | Fired when rule is removed |
#### Internal State
- `selectedProperty`: Currently selected field
- `selectedFunction`: Currently selected function
- `operatorValue`: Current comparison operator
- `valuesValue`: Current rule values
- `isEditing`: Whether in edit mode
- `showPropertySelector`: Whether property selector is open
- `parametersExpanded`: Whether function parameters are expanded
#### Example Usage
```vue
<template>
<RuleRow
:rule="ruleData"
:group-id="'group-1'"
:entity-name="'User'"
@remove="handleRemove"
/>
</template>
```
### ExpressionRow Component
Custom expression rule component for JavaScript expressions.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `rule` | `RuleNodeDto` | Required | Rule data structure |
| `groupId` | `string` | Required | Parent group ID |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `remove` | `void` | Fired when rule is removed |
#### Internal State
- `isEditing`: Whether in edit mode
- `draftExpression`: Current expression being edited
- `isValid`: Whether current expression is valid
#### Example Usage
```vue
<template>
<ExpressionRow
:rule="expressionRule"
:group-id="'group-1'"
@remove="handleRemove"
/>
</template>
```
### ReferenceRow Component
Reusable rule reference component.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `rule` | `RuleNodeDto` | Required | Rule data structure |
| `groupId` | `string` | Required | Parent group ID |
| `entityName` | `string` | `undefined` | Entity name for API calls |
| `availableReferences` | `Reference[]` | `[]` | Available rule references |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `remove` | `void` | Fired when rule is removed |
#### Internal State
- `selectedReference`: Currently selected reference
- `referenceOptions`: Available reference options
- `isValid`: Whether selected reference is valid
#### Example Usage
```vue
<template>
<ReferenceRow
:rule="referenceRule"
:group-id="'group-1'"
:available-references="references"
@remove="handleRemove"
/>
</template>
```
### AddRuleDropdown Component
Dropdown component for adding new rules.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `groupId` | `string` | Required | Parent group ID |
| `size` | `'sm' \| 'default'` | `'default'` | Button size |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `addRule` | `[groupId: string, kind: NodeKind]` | Fired when a rule is added |
#### Internal State
- `isOpen`: Whether dropdown is open
- `features`: Available features from store
#### Example Usage
```vue
<template>
<AddRuleDropdown
:group-id="'group-1'"
size="sm"
@add-rule="handleAddRule"
/>
</template>
```
### ValueSelector Component
Component for selecting and configuring rule values.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `source` | `ValueSource` | Required | Value source type |
| `leftFieldPath` | `string` | `''` | Left field path for context |
| `leftFieldType` | `string` | `'string'` | Left field type |
| `multiValues` | `RuleValueType[]` | `[]` | Multiple values |
| `inlineParameters` | `boolean` | `false` | Show parameters inline |
| `maxItems` | `number` | `undefined` | Maximum number of items |
| `requiredBadgeText` | `string` | `undefined` | Required badge text |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `update:source` | `ValueSource` | Fired when source changes |
| `update:multiValues` | `RuleValueType[]` | Fired when values change |
#### Internal State
- `selectedSource`: Currently selected source
- `contextualData`: Available contextual data
- `loading`: Whether data is loading
#### Example Usage
```vue
<template>
<ValueSelector
:source="ValueSource.LITERAL"
:left-field-path="'user.email'"
:left-field-type="'string'"
:multi-values="values"
@update:source="handleSourceChange"
@update:multi-values="handleValuesChange"
/>
</template>
```
### PropertySelectorInput Component
Input component for selecting properties and functions.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `modelValue` | `ParameterValueDto` | `undefined` | Current value |
| `instanceId` | `string` | `undefined` | Instance ID |
| `entityName` | `string` | Required | Entity name |
| `viewName` | `string` | `undefined` | View name |
| `parentEntityName` | `string` | `undefined` | Parent entity name |
| `availableTabs` | `string[]` | `['properties', 'functions', 'expression']` | Available tabs |
| `dataProviders` | `DataProviders` | `undefined` | Data providers |
| `treeData` | `TreeNode[]` | `undefined` | Static tree data |
| `treeUrl` | `string` | `undefined` | Tree data URL |
| `treeNodesUrl` | `string` | `undefined` | Tree nodes URL |
| `childNodesUrl` | `string` | `undefined` | Child nodes URL |
| `functionsUrl` | `string` | `undefined` | Functions URL |
| `enableExpression` | `boolean` | `false` | Enable expression mode |
| `allowObjectSelection` | `boolean` | `false` | Allow object selection |
| `autoFocusTree` | `boolean` | `false` | Auto focus tree |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `update:modelValue` | `ParameterValueDto` | Fired when value changes |
| `selectProperty` | `TreeNode` | Fired when property is selected |
| `selectFunction` | `FunctionCallDto` | Fired when function is selected |
| `applyExpression` | `string` | Fired when expression is applied |
#### Example Usage
```vue
<template>
<PropertySelectorInput
v-model="selectedValue"
:entity-name="'User'"
:data-providers="providers"
:enable-expression="true"
@select-property="handlePropertySelect"
@select-function="handleFunctionSelect"
/>
</template>
```
### DeveloperConsole Component
Developer tools panel for debugging and testing.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `instanceId` | `string` | Required | Instance ID |
| `isVisible` | `boolean` | `false` | Whether console is visible |
| `defaultHeight` | `number` | `400` | Default height in pixels |
| `rule` | `RuleNodeDto` | Required | Current rule |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `close` | `void` | Fired when console is closed |
| `resize` | `number` | Fired when console is resized |
#### Internal State
- `activeTab`: Currently active tab
- `height`: Current height
#### Example Usage
```vue
<template>
<DeveloperConsole
:instance-id="'my-instance'"
:is-visible="showConsole"
:rule="currentRule"
@close="handleClose"
@resize="handleResize"
/>
</template>
```
### ValidationPanel Component
Panel for displaying validation results and errors.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `rule` | `RuleNodeDto` | Required | Rule to validate |
#### Internal State
- `validationResult`: Current validation result
- `totalRules`: Total number of rules
- `totalGroups`: Total number of groups
#### Example Usage
```vue
<template>
<ValidationPanel
:rule="currentRule"
/>
</template>
```
### TestPanel Component
Panel for testing rules against sample data.
#### Props
None (uses store instance)
#### Internal State
- `mode`: Test mode ('entity' or 'json')
- `testData`: Test data
- `isTesting`: Whether test is running
- `testResult`: Test result
#### Example Usage
```vue
<template>
<TestPanel />
</template>
```
### JsonEditor Component
JSON editor for rule data.
#### Props
None (uses store instance)
#### Internal State
- `jsonText`: JSON text content
- `error`: JSON parsing error
- `isValid`: Whether JSON is valid
#### Example Usage
```vue
<template>
<JsonEditor />
</template>
```
### AutosaveHistory Component
Component for managing autosave history.
#### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `instanceId` | `string` | Required | Instance ID |
#### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `restore` | `AutosaveEntry` | Fired when entry is restored |
| `delete` | `string` | Fired when entry is deleted |
#### Internal State
- `entries`: Autosave entries
- `loading`: Whether loading
- `selectedEntry`: Currently selected entry
#### Example Usage
```vue
<template>
<AutosaveHistory
:instance-id="'my-instance'"
@restore="handleRestore"
@delete="handleDelete"
/>
</template>
```
## ๐ช Store API
### useRuleBuilderStore()
The main Pinia store for managing rule builder state.
```typescript
const store = useRuleBuilderStore()
// Store methods
store.addRule(instanceId: string, groupId: string, kind?: NodeKind)
store.removeRule(instanceId: string, groupId: string, ruleId: string)
store.updateRule(instanceId: string, groupId: string, ruleId: string, updates: Partial<RuleNodeDto>)
store.validateRules(rule: RuleNodeDto): ValidationResult
store.testRule(instanceId: string): Promise<TestResult>
store.generateNaturalLanguage(rule: RuleNodeDto): string
store.generateLambdaExpression(rule: RuleNodeDto): string
```
### useRuleBuilderInstance(instanceId: string)
Get a scoped instance of the rule builder store.
```typescript
const instance = useRuleBuilderInstance('my-instance')
// Instance methods
instance.addRule(groupId: string, kind?: NodeKind)
instance.removeRule(groupId: string, ruleId: string)
instance.updateRule(groupId: string, ruleId: string, updates: Partial<RuleNodeDto>)
instance.validateRules(rule: RuleNodeDto): ValidationResult
instance.testRule(): Promise<TestResult>
```
## ๐ง Data Providers
### DataProviders Interface
```typescript
interface DataProviders {
fetchBaseConfig?: (params: {
entityName: string
viewName?: string
parentEntityName?: string
}) => Promise<{
functions?: FunctionCallDto[]
operators?: {
comparison?: OperatorInfo[]
group?: Array<{ value: 'AND' | 'OR'; label: string }>
}
availableTabs?: string[]
availableProperties?: TreeNode[]
}>
fetchTree?: (params: {
entityName: string
viewName?: string
parentEntityName?: string
path?: string
}) => Promise<TreeNode[]>
testRule?: (rule: RuleNodeDto, testData: any) => Promise<any>
fetchContextualData?: (fieldPath: string) => Promise<any[]>
}
```
### Complete Implementation Example
#### Backend API Endpoints
```csharp
// ASP.NET Core Controller
[ApiController]
[Route("api/rule-builder")]
public class RuleBuilderController : ControllerBase
{
[HttpPost("config")]
public async Task<IActionResult> GetBaseConfig([FromBody] ConfigRequest request)
{
var config = await _ruleBuilderService.GetBaseConfigAsync(
request.EntityName,
request.ViewName,
request.ParentEntityName
);
return Ok(new
{
functions = config.Functions,
operators = new
{
comparison = config.Operators,
group = new[]
{
new { value = "AND", label = "And" },
new { value = "OR", label = "Or" }
}
},
availableTabs = new[] { "properties", "functions", "expression" },
availableProperties = config.AvailableProperties
});
}
[HttpGet("tree")]
public async Task<IActionResult> GetTree([FromQuery] TreeRequest request)
{
var tree = await _ruleBuilderService.GetTreeAsync(
request.EntityName,
request.ViewName,
request.ParentEntityName,
request.Path
);
return Ok(tree);
}
[HttpPost("test")]
public async Task<IActionResult> TestRule([FromBody] TestRuleRequest request)
{
var result = await _ruleBuilderService.TestRuleAsync(
request.Rule,
request.TestData
);
return Ok(result);
}
[HttpGet("contextual-data")]
public async Task<IActionResult> GetContextualData([FromQuery] string field)
{
var data = await _ruleBuilderService.GetContextualDataAsync(field);
return Ok(data);
}
}
```
#### Frontend Implementation
```typescript
// Vue component
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RuleBuilder, useRuleBuilderInstance } from 'vue-rule-builder'
import type { DataProviders, RuleNodeDto } from 'vue-rule-builder'
const myRule = ref<RuleNodeDto>({
id: 'root',
kind: 0,
combinator: 0,
not: false,
children: []
})
const dataProviders: DataProviders = {
fetchBaseConfig: async ({ entityName, viewName, parentEntityName }) => {
const response = await fetch('/api/rule-builder/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ entityName, viewName, parentEntityName })
})
if (!response.ok) {
throw new Error(`Failed to fetch config: ${response.statusText}`)
}
return response.json()
},
fetchTree: async ({ entityName, viewName, parentEntityName, path }) => {
const params = new URLSearchParams({ entityName })
if (viewName) params.append('viewName', viewName)
if (parentEntityName) params.append('parentEntityName', parentEntityName)
if (path) params.append('path', path)
const response = await fetch(`/api/rule-builder/tree?${params}`)
if (!response.ok) {
throw new Error(`Failed to fetch tree: ${response.statusText}`)
}
return response.json()
},
testRule: async (rule, testData) => {
const response = await fetch('/api/rule-builder/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rule, testData })
})
if (!response.ok) {
throw new Error(`Failed to test rule: ${response.statusText}`)
}
return response.json()
},
fetchContextualData: async (fieldPath) => {
const response = await fetch(`/api/rule-builder/contextual-data?field=${encodeURIComponent(fieldPath)}`)
if (!response.ok) {
throw new Error(`Failed to fetch contextual data: ${response.statusText}`)
}
return response.json()
}
}
const handleValidation = (result: { valid: boolean; errors: string[] }) => {
console.log('Validation result:', result)
}
const handleError = (error: Error) => {
console.error('Rule builder error:', error)
}
</script>
<template>
<RuleBuilder
v-model:rule="myRule"
:entity-name="'Product'"
:data-providers="dataProviders"
@validation-change="handleValidation"
@error="handleError"
/>
</template>
```
### Error Handling
#### Best Practices
1. **Always handle errors gracefully**
2. **Provide meaningful error messages**
3. **Implement retry logic for network failures**
4. **Log errors for debugging**
#### Example Error Handling
```typescript
const dataProviders: DataProviders = {
fetchBaseConfig: async (params) => {
try {
const response = await fetch('/api/rule-builder/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
})
if (!response.ok) {
if (response.status === 404) {
throw new Error('Configuration not found for this entity')
} else if (response.status === 403) {
throw new Error('Access denied to configuration')
} else {
throw new Error(`Server error: ${response.statusText}`)
}
}
return response.json()
} catch (error) {
console.error('Failed to fetch base config:', error)
// Return fallback configuration
return {
functions: [],
operators: {
comparison: [
{ value: 'equals', label: 'Equals', supportedTypes: ['string', 'number'], arity: 2 },
{ value: 'notEquals', label: 'Not Equals', supportedTypes: ['string', 'number'], arity: 2 }
],
group: [
{ value: 'AND', label: 'And' },
{ value: 'OR', label: 'Or' }
]
},
availableTabs: ['properties'],
availableProperties: []
}
}
}
}
```
### Caching
#### Implementation
```typescript
class CachedDataProvider implements DataProviders {
private cache = new Map<string, { data: any; timestamp: number }>()
private readonly CACHE_TTL = 5 * 60 * 1000 // 5 minutes
private getCacheKey(endpoint: string, params: any): string {
return `${endpoint}:${JSON.stringify(params)}`
}
private isCacheValid(timestamp: number): boolean {
return Date.now() - timestamp < this.CACHE_TTL
}
async fetchBaseConfig(params: any) {
const key = this.getCacheKey('config', params)
const cached = this.cache.get(key)
if (cached && this.isCacheValid(cached.timestamp)) {
return cached.data
}
const data = await this.fetchFromAPI('/api/rule-builder/config', params)
this.cache.set(key, { data, timestamp: Date.now() })
return data
}
private async fetchFromAPI(endpoint: string, params: any) {
// Implementation here
}
}
```
### Performance Optimization
#### Lazy Loading
```typescript
const dataProviders: DataProviders = {
fetchTree: async ({ entityName, path }) => {
// Only fetch if path is provided (lazy loading)
if (!path) {
return []
}
const response = await fetch(`/api/rule-builder/tree?entityName=${entityName}&path=${path}`)
return response.json()
}
}
```
#### Debouncing
```typescript
import { debounce } from 'lodash-es'
const debouncedFetchTree = debounce(async (params) => {
const response = await fetch('/api/rule-builder/tree', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
})
return response.json()
}, 300)
const dataProviders: DataProviders = {
fetchTree: debouncedFetchTree
}
```
### Testing
#### Mock Implementation
```typescript
const mockDataProviders: DataProviders = {
fetchBaseConfig: async () => ({
functions: [
{
name: 'calculateAge',
returnType: 'number',
parameters: [
{ name: 'birthDate', type: 'date', required: true }
],
description: 'Calculate age from birth date'
}
],
operators: {
comparison: [
{ value: 'equals', label: 'Equals', supportedTypes: ['string', 'number'], arity: 2 }
],
group: [
{ value: 'AND', label: 'And' },
{ value: 'OR', label: 'Or' }
]
},
availableTabs: ['properties', 'functions'],
availableProperties: []
}),
fetchTree: async () => [
{
id: 'user',
name: 'User',
type: 'object',
path: 'user',
isExpandable: true,
icon: 'User'
}
],
testRule: async (rule, testData) => ({
success: true,
result: true,
executionTime: 10
}),
fetchContextualData: async () => [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' }
]
}
```
## ๐จ Customization
### Feature Flags
Control which features are available in the rule builder:
```typescript
interface FeatureFlags {
enableRules?: boolean // Enable property-based rules
enableExpressions?: boolean // Enable custom expressions
enableReferences?: boolean // Enable rule references
enableNotOperator?: boolean // Enable NOT operator
enableDeveloperTools?: boolean // Enable developer console
}
```
### Theming
The component uses CSS custom properties for theming:
```css
:root {
--rule-builder-primary: #3b82f6;
--rule-builder-primary-hover: #2563eb;
--rule-builder-danger: #ef4444;
--rule-builder-danger-hover: #dc2626;
--rule-builder-success: #10b981;
--rule-builder-warning: #f59e0b;
--rule-builder-background: #ffffff;
--rule-builder-surface: #f9fafb;
--rule-builder-border: #e5e7eb;
--rule-builder-text: #111827;
--rule-builder-text-muted: #6b7280;
}
```
### Custom Styling
```vue
<template>
<RuleBuilder
class="my-rule-builder"
v-model:rule="myRule"
/>
</template>
<style>
.my-rule-builder {
--rule-builder-primary: #8b5cf6;
--rule-builder-primary-hover: #7c3aed;
}
.my-rule-builder .rule-group {
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
</style>
```
## ๐งช Testing
### Test Panel
The rule builder includes a built-in test panel for validating rules:
```vue
<template>
<RuleBuilder
v-model:rule="myRule"
:data-providers="dataProviders"
/>
</template>
<script setup>
const dataProviders = {
testRule: async (rule, testData) => {
// Your test implementation
const response = await fetch('/api/rules/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rule, testData })
})
return response.json()
}
}
</script>
```
### Validation
```typescript
const store = useRuleBuilderStore()
const validation = store.validateRules(myRule.value)
if (!validation.isValid) {
console.log('Validation errors:', validation.errors)
}
```
## ๐ Data Structures
### RuleNodeDto
The core data structure representing a rule:
```typescript
interface RuleNodeDto {
id: string
kind: NodeKind // 0: Group, 1: Rule, 2: Expression, 3: Reference
not: boolean // NOT operator
children: RuleNodeDto[] // Child nodes
combinator?: CombinatorKind // AND/OR operator for groups
field?: string // Field path for property rules
operator?: string // Comparison operator
values?: ParameterValueDto[] // Rule values
expression?: string // Custom expression
function?: FunctionCallDto // Function call for property rules
referenceId?: string // Reference ID for reference rules
referenceName?: string // Reference name for display
}
```
### TreeNode
Field definition structure:
```typescript
interface TreeNode {
id: string // Unique identifier
name: string // Display name
type: string // Data type: 'string', 'number', 'boolean', 'date', 'object', 'array'
path: string // Property path (e.g., 'user.email')
isExpandable: boolean // Whether this node can be expanded
icon?: string // Icon name (optional)
parentId?: string // Parent node ID (for child nodes)
children?: TreeNode[] // Child nodes (for expandable nodes)
capabilities?: number // Field capabilities for operator filtering
}
```
### ValidationResult
Validation result structure:
```typescript
interface ValidationResult {
isValid: boolean
errors: Array<{
message: string
path?: string
nodeId?: string
}>
}
```
### FunctionCallDto
Function definition structure:
```typescript
interface FunctionCallDto {
name: string
returnType: string
parameters?: Array<{
name: string
type: string
required: boolean
description?: string
}>
description?: string
example?: string
category?: string
}
```
### OperatorInfo
Operator definition structure:
```typescript
interface OperatorInfo {
value: string
label: string
supportedTypes: string[]
arity: OperatorArity // Unary, Binary, Ternary, Variadic
description?: string
}
```
## ๐ Advanced Usage
### Multi-Instance Support
```vue
<template>
<div>
<RuleBuilder
:instance-id="'user-rules'"
v-model:rule="userRules"
/>
<RuleBuilder
:instance-id="'product-rules'"
v-model:rule="productRules"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { RuleBuilder } from 'vue-rule-builder'
const userRules = ref({ /* ... */ })
const productRules = ref({ /* ... */ })
</script>
```
### Custom Operators
```typescript
const customOperators = [
{
value: 'customOperator',
label: 'Custom Operator',
supportedTypes: ['string', 'number'],
arity: 2 // Binary operator
}
]
const dataProviders = {
fetchBaseConfig: async () => ({
operators: {
comparison: customOperators
}
})
}
```
### Custom Functions
```typescript
const customFunctions = [
{
name: 'calculateAge',
returnType: 'number',
parameters: [
{ name: 'birthDate', type: 'date', required: true }
],
description: 'Calculate age from birth date'
}
]
const dataProviders = {
fetchBaseConfig: async () => ({
functions: customFunctions
})
}
```
## ๐ค Contributing
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/amazing-feature`
3. Commit your changes: `git commit -m 'Add amazing feature'`
4. Push to the branch: `git push origin feature/amazing-feature`
5. Open a Pull Request
## ๐ License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## ๐ Support
- ๐ [Documentation](https://github.com/your-org/vue-rule-builder)
- ๐ [Issue Tracker](https://github.com/your-org/vue-rule-builder/issues)
- ๐ฌ [Discussions](https://github.com/your-org/vue-rule-builder/discussions)
---
Made with โค๏ธ by the Vue Rule Builder team