@praxisui/visual-builder 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +650 -0
- package/fesm2022/praxisui-visual-builder.mjs +21620 -0
- package/fesm2022/praxisui-visual-builder.mjs.map +1 -0
- package/index.d.ts +3600 -0
- package/package.json +42 -0
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
# Praxis Visual Builder
|
|
2
|
+
|
|
3
|
+
A comprehensive Angular library for building visual rule editors with support for mini-DSL expressions and contextual specifications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### 🎯 **Core Components**
|
|
8
|
+
|
|
9
|
+
- **ExpressionEditorComponent**: Advanced DSL expression editor with syntax highlighting and autocomplete
|
|
10
|
+
- **ContextVariableManagerComponent**: Complete context variable management system
|
|
11
|
+
- **SpecificationBridgeService**: Bidirectional conversion between visual rules and DSL expressions
|
|
12
|
+
|
|
13
|
+
### ⚡ **Advanced Capabilities**
|
|
14
|
+
|
|
15
|
+
- **Mini-DSL Support**: Full DSL parsing, validation, and round-trip conversion
|
|
16
|
+
- **Context Variables**: Dynamic variable resolution with scoped contexts
|
|
17
|
+
- **Real-time Validation**: Live syntax checking with performance metrics
|
|
18
|
+
- **Autocomplete**: Intelligent suggestions for fields, functions, operators, and variables
|
|
19
|
+
- **Round-trip Integrity**: Seamless conversion between visual and textual representations
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @praxis/visual-builder
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Basic Expression Editor
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { ExpressionEditorComponent } from "@praxis/visual-builder";
|
|
33
|
+
|
|
34
|
+
@Component({
|
|
35
|
+
template: ` <praxis-expression-editor [fieldSchemas]="fieldSchemas" [contextVariables]="contextVariables" [(expression)]="expression" (validationChange)="onValidationChange($event)"> </praxis-expression-editor> `,
|
|
36
|
+
})
|
|
37
|
+
export class MyComponent {
|
|
38
|
+
fieldSchemas = [
|
|
39
|
+
{ name: "age", type: "number", label: "Age" },
|
|
40
|
+
{ name: "name", type: "string", label: "Name" },
|
|
41
|
+
{ name: "active", type: "boolean", label: "Active" },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
contextVariables = [{ name: "user.minAge", type: "number", scope: "user", example: "18" }];
|
|
45
|
+
|
|
46
|
+
expression = "age > ${user.minAge} && active == true";
|
|
47
|
+
|
|
48
|
+
onValidationChange(result: ExpressionValidationResult) {
|
|
49
|
+
console.log("Expression valid:", result.isValid);
|
|
50
|
+
console.log("Issues:", result.issues);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Context Variable Management
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { ContextVariableManagerComponent } from "@praxis/visual-builder";
|
|
59
|
+
|
|
60
|
+
@Component({
|
|
61
|
+
template: ` <praxis-context-variable-manager [(contextVariables)]="contextVariables" [allowedScopes]="allowedScopes" (variableAdd)="onVariableAdd($event)" (variableUpdate)="onVariableUpdate($event)"> </praxis-context-variable-manager> `,
|
|
62
|
+
})
|
|
63
|
+
export class VariableManagementComponent {
|
|
64
|
+
contextVariables: EnhancedContextVariable[] = [];
|
|
65
|
+
allowedScopes = ["user", "session", "global"];
|
|
66
|
+
|
|
67
|
+
onVariableAdd(variable: EnhancedContextVariable) {
|
|
68
|
+
console.log("New variable added:", variable);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Visual Rule Editor in Embedded Mode
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { RuleEditorComponent } from "@praxis/visual-builder";
|
|
77
|
+
|
|
78
|
+
@Component({
|
|
79
|
+
template: ` <praxis-rule-editor [config]="builderConfig" [embedded]="true" (rulesChanged)="onRulesChanged($event)"> </praxis-rule-editor> `,
|
|
80
|
+
})
|
|
81
|
+
export class EmbeddedRuleEditor {
|
|
82
|
+
builderConfig = {
|
|
83
|
+
/* ... */
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
onRulesChanged(rules: any) {
|
|
87
|
+
console.log("Updated rules:", rules);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`embedded` mode allows the editor to fit within existing layouts without spawning nested scrollbars, making it suitable for corporate dashboards and configuration panels where space is constrained.
|
|
93
|
+
|
|
94
|
+
## Mini-DSL Language Guide
|
|
95
|
+
|
|
96
|
+
### Basic Syntax
|
|
97
|
+
|
|
98
|
+
The mini-DSL supports a rich expression language for building validation rules and conditions.
|
|
99
|
+
|
|
100
|
+
#### Field References
|
|
101
|
+
|
|
102
|
+
```dsl
|
|
103
|
+
age > 18
|
|
104
|
+
name == "John Doe"
|
|
105
|
+
active == true
|
|
106
|
+
score >= 80
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Context Variables
|
|
110
|
+
|
|
111
|
+
Context variables are referenced using `${scope.variable}` syntax:
|
|
112
|
+
|
|
113
|
+
```dsl
|
|
114
|
+
age > ${user.minAge}
|
|
115
|
+
department == "${user.department}"
|
|
116
|
+
level >= ${policy.requiredLevel}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### Operators
|
|
120
|
+
|
|
121
|
+
**Comparison Operators:**
|
|
122
|
+
|
|
123
|
+
```dsl
|
|
124
|
+
age == 25 # Equality
|
|
125
|
+
age != 30 # Inequality
|
|
126
|
+
age > 18 # Greater than
|
|
127
|
+
age >= 21 # Greater than or equal
|
|
128
|
+
age < 65 # Less than
|
|
129
|
+
age <= 64 # Less than or equal
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Logical Operators:**
|
|
133
|
+
|
|
134
|
+
```dsl
|
|
135
|
+
age > 18 && active == true # AND
|
|
136
|
+
priority == "high" || urgent == true # OR
|
|
137
|
+
!(deleted == true) # NOT
|
|
138
|
+
status == "A" ^ backup == true # XOR (exclusive or)
|
|
139
|
+
qualified == true => approved == true # IMPLIES
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Membership Operators:**
|
|
143
|
+
|
|
144
|
+
```dsl
|
|
145
|
+
category in ["tech", "science", "math"] # IN (array membership)
|
|
146
|
+
role not in ["guest", "anonymous"] # NOT IN
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Null Checks:**
|
|
150
|
+
|
|
151
|
+
```dsl
|
|
152
|
+
description != null # Not null
|
|
153
|
+
optional == null # Is null
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Functions
|
|
157
|
+
|
|
158
|
+
**String Functions:**
|
|
159
|
+
|
|
160
|
+
```dsl
|
|
161
|
+
contains(tags, "important") # Check if string/array contains value
|
|
162
|
+
startsWith(email, "admin") # Check if string starts with value
|
|
163
|
+
endsWith(filename, ".pdf") # Check if string ends with value
|
|
164
|
+
length(description) > 10 # Get string/array length
|
|
165
|
+
upper(category) == "TECHNOLOGY" # Convert to uppercase
|
|
166
|
+
lower(name) == "john" # Convert to lowercase
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Collection Functions:**
|
|
170
|
+
|
|
171
|
+
```dsl
|
|
172
|
+
atLeast(2, [condition1, condition2, condition3]) # At least N conditions true
|
|
173
|
+
exactly(1, [optionA, optionB, optionC]) # Exactly N conditions true
|
|
174
|
+
forEach(items, itemCondition) # All items satisfy condition
|
|
175
|
+
uniqueBy(collection, ["field1", "field2"]) # Unique by specified fields
|
|
176
|
+
minLength(array, 3) # Minimum array length
|
|
177
|
+
maxLength(array, 10) # Maximum array length
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Conditional Functions:**
|
|
181
|
+
|
|
182
|
+
```dsl
|
|
183
|
+
requiredIf(field, condition) # Field required if condition true
|
|
184
|
+
visibleIf(field, condition) # Field visible if condition true
|
|
185
|
+
disabledIf(field, condition) # Field disabled if condition true
|
|
186
|
+
readonlyIf(field, condition) # Field readonly if condition true
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Optional Handling:**
|
|
190
|
+
|
|
191
|
+
```dsl
|
|
192
|
+
ifDefined(optionalField, condition) # Apply condition only if field defined
|
|
193
|
+
ifNotNull(field, condition) # Apply condition only if field not null
|
|
194
|
+
ifExists(field, condition) # Apply condition only if field exists
|
|
195
|
+
withDefault(field, defaultValue, condition) # Use default if field undefined
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Complex Expressions
|
|
199
|
+
|
|
200
|
+
#### Nested Conditions
|
|
201
|
+
|
|
202
|
+
```dsl
|
|
203
|
+
((age >= 18 && age <= 65) || experience > 10) &&
|
|
204
|
+
(department == "Engineering" || department == "Product") &&
|
|
205
|
+
!(archived == true || deleted == true)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### Mixed Context and Fields
|
|
209
|
+
|
|
210
|
+
```dsl
|
|
211
|
+
salary >= ${policy.minSalary} &&
|
|
212
|
+
salary <= ${policy.maxSalary} &&
|
|
213
|
+
location in ${user.allowedLocations} &&
|
|
214
|
+
startDate >= "${session.currentDate}"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Function Composition
|
|
218
|
+
|
|
219
|
+
```dsl
|
|
220
|
+
contains(upper(department), "ENG") &&
|
|
221
|
+
length(trim(description)) > ${config.minDescLength} &&
|
|
222
|
+
endsWith(lower(email), "@company.com")
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Context Variables
|
|
226
|
+
|
|
227
|
+
Context variables provide dynamic values that can be resolved at runtime based on different scopes.
|
|
228
|
+
|
|
229
|
+
#### Scopes
|
|
230
|
+
|
|
231
|
+
**User Scope** (`user.*`):
|
|
232
|
+
|
|
233
|
+
- User-specific values (preferences, profile data)
|
|
234
|
+
- Example: `${user.department}`, `${user.role}`, `${user.level}`
|
|
235
|
+
|
|
236
|
+
**Session Scope** (`session.*`):
|
|
237
|
+
|
|
238
|
+
- Session-specific values (temporary data, current state)
|
|
239
|
+
- Example: `${session.currentDate}`, `${session.userId}`, `${session.locale}`
|
|
240
|
+
|
|
241
|
+
**Environment Scope** (`env.*`):
|
|
242
|
+
|
|
243
|
+
- Environment configuration (debug flags, feature toggles)
|
|
244
|
+
- Example: `${env.debug}`, `${env.features.newUI}`, `${env.apiVersion}`
|
|
245
|
+
|
|
246
|
+
**Global Scope** (`global.*`):
|
|
247
|
+
|
|
248
|
+
- Application-wide constants (policies, configurations)
|
|
249
|
+
- Example: `${global.minAge}`, `${global.maxFileSize}`, `${global.supportedFormats}`
|
|
250
|
+
|
|
251
|
+
#### Variable Types
|
|
252
|
+
|
|
253
|
+
**String Variables:**
|
|
254
|
+
|
|
255
|
+
```dsl
|
|
256
|
+
name == "${user.fullName}"
|
|
257
|
+
department == "${user.department}"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Number Variables:**
|
|
261
|
+
|
|
262
|
+
```dsl
|
|
263
|
+
age > ${user.minAge}
|
|
264
|
+
salary >= ${policy.baseSalary}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Boolean Variables:**
|
|
268
|
+
|
|
269
|
+
```dsl
|
|
270
|
+
active == ${user.isActive}
|
|
271
|
+
debug == ${env.debugMode}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Array Variables:**
|
|
275
|
+
|
|
276
|
+
```dsl
|
|
277
|
+
category in ${user.allowedCategories}
|
|
278
|
+
format in ${global.supportedFormats}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Date Variables:**
|
|
282
|
+
|
|
283
|
+
```dsl
|
|
284
|
+
createdDate >= "${session.startDate}"
|
|
285
|
+
expirationDate <= "${policy.maxExpirationDate}"
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Validation and Error Handling
|
|
289
|
+
|
|
290
|
+
#### Syntax Errors
|
|
291
|
+
|
|
292
|
+
The DSL parser provides detailed error messages for syntax issues:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// Invalid syntax examples and their error messages:
|
|
296
|
+
|
|
297
|
+
// "age >" -> "Missing operand after comparison operator"
|
|
298
|
+
// "age >> 18" -> "Unknown operator '>>'"
|
|
299
|
+
// "(age > 18" -> "Unclosed parentheses"
|
|
300
|
+
// "unknownFunc(age)" -> "Unknown function 'unknownFunc'"
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### Field Validation
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// Unknown field warnings:
|
|
307
|
+
// "unknownField == 'test'" -> "Unknown field: unknownField. Did you mean 'knownField'?"
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### Context Variable Validation
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Missing context variable warnings:
|
|
314
|
+
// "age > ${missing.variable}" -> "Unknown context variable: missing.variable"
|
|
315
|
+
// With suggestions: "Did you mean 'existing.variable'?"
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### Performance Warnings
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// Complex expression warnings:
|
|
322
|
+
// For expressions with >50 operators:
|
|
323
|
+
// "Expression complexity is high (67 operators). Consider breaking into smaller expressions."
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## API Reference
|
|
327
|
+
|
|
328
|
+
### Exports
|
|
329
|
+
|
|
330
|
+
Main exports available from `@praxis/visual-builder` (see `projects/praxis-visual-builder/src/public-api.ts`):
|
|
331
|
+
|
|
332
|
+
- Components: `RuleEditorComponent`, `RuleCanvasComponent`, `RuleNodeComponent`, `FieldConditionEditorComponent`, `ConditionalValidatorEditorComponent`, `CollectionValidatorEditorComponent`, `MetadataEditorComponent`, `DslViewerComponent`, `JsonViewerComponent`, `RoundTripTesterComponent`, `ExportDialogComponent`, `DslLinterComponent`, `VisualRuleBuilderComponent`, `TemplateGalleryComponent`, `TemplateEditorDialogComponent`, `TemplatePreviewDialogComponent`
|
|
333
|
+
- Services: `SpecificationBridgeService`, `RuleBuilderService`, `RoundTripValidatorService`, `ExportIntegrationService`, `WebhookIntegrationService`, `RuleTemplateService`, `RuleValidationService`, `RuleNodeRegistryService`, `ContextManagementService`, `FieldSchemaService`, `RuleConversionService`, `DslParsingService`
|
|
334
|
+
- Models/Types: `FieldSchema`, `RuleBuilderConfig`, `ArrayFieldSchema`, `ContextScope`, `ContextEntry`, `ContextValue`, `SpecificationContextualConfig`
|
|
335
|
+
- Errors: `VisualBuilderError`, `VBValidationError`, `ConversionError`, `RegistryError`, `DslError`, `ContextError`, `ConfigurationError`, `InternalError`, `ErrorCategory`, `ErrorSeverity`, `ErrorHandler`, `globalErrorHandler`, `createError`, `ErrorInfo`, `ErrorStatistics`
|
|
336
|
+
|
|
337
|
+
### ExpressionEditorComponent
|
|
338
|
+
|
|
339
|
+
#### Inputs
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
@Input() expression: string = ''; // Current DSL expression
|
|
343
|
+
@Input() fieldSchemas: FieldSchema[] = []; // Available fields for autocomplete
|
|
344
|
+
@Input() contextVariables: ContextVariable[] = []; // Available context variables
|
|
345
|
+
@Input() functionRegistry?: FunctionRegistry; // Custom function registry
|
|
346
|
+
@Input() validationConfig?: ValidationConfig; // Validation configuration
|
|
347
|
+
@Input() editorOptions?: EditorOptions; // Editor display options
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Outputs
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
@Output() expressionChange = new EventEmitter<string>(); // Expression changes
|
|
354
|
+
@Output() validationChange = new EventEmitter<ExpressionValidationResult>(); // Validation updates
|
|
355
|
+
@Output() suggestionRequest = new EventEmitter<SuggestionContext>(); // Autocomplete requests
|
|
356
|
+
@Output() focusChange = new EventEmitter<boolean>(); // Focus state changes
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### Methods
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// Get autocomplete suggestions
|
|
363
|
+
getSuggestions(text: string, position: number): Promise<DslSuggestion[]>
|
|
364
|
+
|
|
365
|
+
// Validate current expression
|
|
366
|
+
validateExpression(): Promise<ExpressionValidationResult>
|
|
367
|
+
|
|
368
|
+
// Insert text at cursor position
|
|
369
|
+
insertTextAtCursor(text: string): void
|
|
370
|
+
|
|
371
|
+
// Format expression
|
|
372
|
+
formatExpression(): void
|
|
373
|
+
|
|
374
|
+
// Clear expression
|
|
375
|
+
clearExpression(): void
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### ContextVariableManagerComponent
|
|
379
|
+
|
|
380
|
+
#### Inputs
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
@Input() contextVariables: EnhancedContextVariable[] = []; // Current variables
|
|
384
|
+
@Input() allowedScopes: ContextScope[] = []; // Allowed variable scopes
|
|
385
|
+
@Input() readOnly: boolean = false; // Read-only mode
|
|
386
|
+
@Input() showCategories: boolean = true; // Show category grouping
|
|
387
|
+
@Input() allowImportExport: boolean = true; // Enable import/export
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Outputs
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
@Output() contextVariablesChange = new EventEmitter<EnhancedContextVariable[]>(); // Variables updated
|
|
394
|
+
@Output() variableAdd = new EventEmitter<EnhancedContextVariable>(); // Variable added
|
|
395
|
+
@Output() variableUpdate = new EventEmitter<EnhancedContextVariable>(); // Variable updated
|
|
396
|
+
@Output() variableDelete = new EventEmitter<string>(); // Variable deleted
|
|
397
|
+
@Output() importComplete = new EventEmitter<ImportResult>(); // Import completed
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
#### Methods
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
// Add new variable
|
|
404
|
+
addVariable(variable: Partial<EnhancedContextVariable>): void
|
|
405
|
+
|
|
406
|
+
// Update existing variable
|
|
407
|
+
updateVariable(id: string, updates: Partial<EnhancedContextVariable>): void
|
|
408
|
+
|
|
409
|
+
// Delete variable
|
|
410
|
+
deleteVariable(id: string): void
|
|
411
|
+
|
|
412
|
+
// Get variables by scope
|
|
413
|
+
getVariablesByScope(scope: string): EnhancedContextVariable[]
|
|
414
|
+
|
|
415
|
+
// Export variables
|
|
416
|
+
exportVariables(format: 'json' | 'csv'): string
|
|
417
|
+
|
|
418
|
+
// Import variables
|
|
419
|
+
importVariables(data: string, format: 'json' | 'csv'): Promise<ImportResult>
|
|
420
|
+
|
|
421
|
+
// Validate variable name
|
|
422
|
+
isValidVariableName(name: string): boolean
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### SpecificationBridgeService
|
|
426
|
+
|
|
427
|
+
#### DSL Expression Methods
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
// Parse DSL expression to specification
|
|
431
|
+
parseDslExpression<T>(expression: string, config?: DslParsingConfig): DslParsingResult<T>
|
|
432
|
+
|
|
433
|
+
// Validate expression round-trip integrity
|
|
434
|
+
validateExpressionRoundTrip<T>(expression: string, config?: DslParsingConfig): RoundTripResult
|
|
435
|
+
|
|
436
|
+
// Create contextual specification
|
|
437
|
+
createContextualSpecification<T>(template: string, config?: ContextualConfig): ContextualSpecification<T>
|
|
438
|
+
|
|
439
|
+
// Resolve context tokens in template
|
|
440
|
+
resolveContextTokens(template: string, variables: ContextVariable[]): string
|
|
441
|
+
|
|
442
|
+
// Extract context tokens from template
|
|
443
|
+
extractContextTokens(template: string): string[]
|
|
444
|
+
|
|
445
|
+
// Validate context tokens
|
|
446
|
+
validateContextTokens(template: string, variables: ContextVariable[]): ValidationIssue[]
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
#### Rule Node Conversion Methods
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// Convert rule node to specification
|
|
453
|
+
ruleNodeToSpecification<T>(node: RuleNode): Specification<T>
|
|
454
|
+
|
|
455
|
+
// Convert specification to rule node
|
|
456
|
+
specificationToRuleNode<T>(spec: Specification<T>): RuleNode
|
|
457
|
+
|
|
458
|
+
// Export rule node to DSL
|
|
459
|
+
exportToDsl<T>(node: RuleNode, options?: ExportOptions): string
|
|
460
|
+
|
|
461
|
+
// Validate round-trip through rule nodes
|
|
462
|
+
validateRoundTrip<T>(node: RuleNode): RoundTripValidationResult
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Context Provider Methods
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
// Update context provider
|
|
469
|
+
updateContextProvider(provider: ContextProvider): void
|
|
470
|
+
|
|
471
|
+
// Get current context provider
|
|
472
|
+
getContextProvider(): ContextProvider | undefined
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Advanced Usage
|
|
476
|
+
|
|
477
|
+
### Custom Function Registry
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import { FunctionRegistry } from '@praxis/specification';
|
|
481
|
+
|
|
482
|
+
// Create custom function registry
|
|
483
|
+
const customRegistry = new FunctionRegistry();
|
|
484
|
+
|
|
485
|
+
// Register custom functions
|
|
486
|
+
customRegistry.register('isEmail', {
|
|
487
|
+
name: 'isEmail',
|
|
488
|
+
arity: 1,
|
|
489
|
+
implementation: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
|
|
490
|
+
description: 'Validates email format'
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
customRegistry.register('dateAdd', {
|
|
494
|
+
name: 'dateAdd',
|
|
495
|
+
arity: 3,
|
|
496
|
+
implementation: (date: Date, amount: number, unit: string) => {
|
|
497
|
+
// Implementation for date arithmetic
|
|
498
|
+
},
|
|
499
|
+
description: 'Adds time to a date'
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Use in expression editor
|
|
503
|
+
<praxis-expression-editor
|
|
504
|
+
[functionRegistry]="customRegistry"
|
|
505
|
+
expression="isEmail(userEmail) && dateAdd(startDate, 30, 'days') > now()">
|
|
506
|
+
</praxis-expression-editor>
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Custom Context Provider
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
import { ContextProvider } from "@praxis/specification";
|
|
513
|
+
|
|
514
|
+
class DatabaseContextProvider implements ContextProvider {
|
|
515
|
+
private cache = new Map<string, any>();
|
|
516
|
+
|
|
517
|
+
async getValue(key: string): Promise<any> {
|
|
518
|
+
if (this.cache.has(key)) {
|
|
519
|
+
return this.cache.get(key);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Fetch from database or API
|
|
523
|
+
const value = await this.fetchFromDatabase(key);
|
|
524
|
+
this.cache.set(key, value);
|
|
525
|
+
return value;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
hasValue(key: string): boolean {
|
|
529
|
+
return this.cache.has(key) || this.isValidKey(key);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
setValue(key: string, value: any): void {
|
|
533
|
+
this.cache.set(key, value);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ... other methods
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Use custom provider
|
|
540
|
+
const provider = new DatabaseContextProvider();
|
|
541
|
+
this.bridgeService.updateContextProvider(provider);
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Integration with Form Builders
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
@Component({
|
|
548
|
+
template: `
|
|
549
|
+
<form [formGroup]="ruleForm">
|
|
550
|
+
<!-- Visual Rule Builder -->
|
|
551
|
+
<praxis-expression-editor formControlName="expression" [fieldSchemas]="formFieldSchemas" [contextVariables]="availableVariables"> </praxis-expression-editor>
|
|
552
|
+
|
|
553
|
+
<!-- Context Variable Management -->
|
|
554
|
+
<praxis-context-variable-manager [(contextVariables)]="availableVariables" [allowedScopes]="['user', 'session']"> </praxis-context-variable-manager>
|
|
555
|
+
|
|
556
|
+
<!-- Generated Rule Preview -->
|
|
557
|
+
<pre>{{ generatedRule | json }}</pre>
|
|
558
|
+
</form>
|
|
559
|
+
`,
|
|
560
|
+
})
|
|
561
|
+
export class RuleBuilderFormComponent {
|
|
562
|
+
ruleForm = this.fb.group({
|
|
563
|
+
expression: ["", Validators.required],
|
|
564
|
+
metadata: this.fb.group({
|
|
565
|
+
name: ["", Validators.required],
|
|
566
|
+
description: [""],
|
|
567
|
+
priority: [1],
|
|
568
|
+
}),
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
get generatedRule() {
|
|
572
|
+
const expression = this.ruleForm.get("expression")?.value;
|
|
573
|
+
if (!expression) return null;
|
|
574
|
+
|
|
575
|
+
const parseResult = this.bridgeService.parseDslExpression(expression, {
|
|
576
|
+
knownFields: this.formFieldSchemas.map((f) => f.name),
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
return parseResult.success ? parseResult.specification?.toJSON() : null;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## Performance Guidelines
|
|
585
|
+
|
|
586
|
+
### Optimization Tips
|
|
587
|
+
|
|
588
|
+
1. **Field Schema Management**:
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
// ✅ Good: Reuse field schema objects
|
|
592
|
+
const fieldSchemas = useMemo(() => fields.map((f) => ({ name: f.name, type: f.type, label: f.label })), [fields]);
|
|
593
|
+
|
|
594
|
+
// ❌ Avoid: Creating new objects on every render
|
|
595
|
+
const fieldSchemas = fields.map((f) => ({ name: f.name, type: f.type }));
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
2. **Expression Validation**:
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
// ✅ Good: Debounce validation
|
|
602
|
+
const debouncedValidation = useMemo(
|
|
603
|
+
() => debounce(validateExpression, 300),
|
|
604
|
+
[]
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
// ❌ Avoid: Validating on every keystroke
|
|
608
|
+
onChange={(expr) => validateExpression(expr)}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
## Building
|
|
612
|
+
|
|
613
|
+
To build the library, run:
|
|
614
|
+
|
|
615
|
+
```bash
|
|
616
|
+
ng build praxis-visual-builder
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
|
|
620
|
+
|
|
621
|
+
### Publishing the Library
|
|
622
|
+
|
|
623
|
+
Once the project is built, you can publish your library by following these steps:
|
|
624
|
+
|
|
625
|
+
1. Navigate to the `dist` directory:
|
|
626
|
+
|
|
627
|
+
```bash
|
|
628
|
+
cd dist/praxis-visual-builder
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
2. Run the `npm publish` command to publish your library to the npm registry:
|
|
632
|
+
```bash
|
|
633
|
+
npm publish
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
## Running unit tests
|
|
637
|
+
|
|
638
|
+
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
|
639
|
+
|
|
640
|
+
```bash
|
|
641
|
+
ng test praxis-visual-builder
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
## Contributing
|
|
645
|
+
|
|
646
|
+
Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
647
|
+
|
|
648
|
+
## License
|
|
649
|
+
|
|
650
|
+
Apache-2.0 – see the `LICENSE` packaged with this library or the repository root.
|