
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@praxisui/visual-builder
Advanced tools
Visual rule and expression builder for Praxis UI with mini-DSL support, validation and context variables.
Para ver esta biblioteca em funcionamento em uma aplicação completa, utilize o projeto de exemplo (Quickstart):
@praxisui/* em um app Angular, incluindo instalação, configuração e uso em telas reais.A comprehensive Angular library for building visual rule editors with support for mini-DSL expressions and contextual specifications.
npm install @praxisui/visual-builder
import { ExpressionEditorComponent } from "@praxisui/visual-builder";
@Component({
template: ` <praxis-expression-editor [fieldSchemas]="fieldSchemas" [contextVariables]="contextVariables" [(expression)]="expression" (validationChange)="onValidationChange($event)"> </praxis-expression-editor> `,
})
export class MyComponent {
fieldSchemas = [
{ name: "age", type: "number", label: "Age" },
{ name: "name", type: "string", label: "Name" },
{ name: "active", type: "boolean", label: "Active" },
];
contextVariables = [{ name: "user.minAge", type: "number", scope: "user", example: "18" }];
expression = "age > ${user.minAge} && active == true";
onValidationChange(result: ExpressionValidationResult) {
console.log("Expression valid:", result.isValid);
console.log("Issues:", result.issues);
}
}
import { ContextVariableManagerComponent } from "@praxisui/visual-builder";
@Component({
template: ` <praxis-context-variable-manager [(contextVariables)]="contextVariables" [allowedScopes]="allowedScopes" (variableAdd)="onVariableAdd($event)" (variableUpdate)="onVariableUpdate($event)"> </praxis-context-variable-manager> `,
})
export class VariableManagementComponent {
contextVariables: EnhancedContextVariable[] = [];
allowedScopes = ["user", "session", "global"];
onVariableAdd(variable: EnhancedContextVariable) {
console.log("New variable added:", variable);
}
}
import { RuleEditorComponent } from "@praxisui/visual-builder";
@Component({
template: ` <praxis-rule-editor [config]="builderConfig" [embedded]="true" (rulesChanged)="onRulesChanged($event)"> </praxis-rule-editor> `,
})
export class EmbeddedRuleEditor {
builderConfig = {
/* ... */
};
onRulesChanged(rules: any) {
console.log("Updated rules:", rules);
}
}
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.
The mini-DSL supports a rich expression language for building validation rules and conditions.
age > 18
name == "John Doe"
active == true
score >= 80
Context variables are referenced using ${scope.variable} syntax:
age > ${user.minAge}
department == "${user.department}"
level >= ${policy.requiredLevel}
Comparison Operators:
age == 25 # Equality
age != 30 # Inequality
age > 18 # Greater than
age >= 21 # Greater than or equal
age < 65 # Less than
age <= 64 # Less than or equal
Logical Operators:
age > 18 && active == true # AND
priority == "high" || urgent == true # OR
!(deleted == true) # NOT
status == "A" ^ backup == true # XOR (exclusive or)
qualified == true => approved == true # IMPLIES
Membership Operators:
category in ["tech", "science", "math"] # IN (array membership)
role not in ["guest", "anonymous"] # NOT IN
Null Checks:
description != null # Not null
optional == null # Is null
String Functions:
contains(tags, "important") # Check if string/array contains value
startsWith(email, "admin") # Check if string starts with value
endsWith(filename, ".pdf") # Check if string ends with value
length(description) > 10 # Get string/array length
upper(category) == "TECHNOLOGY" # Convert to uppercase
lower(name) == "john" # Convert to lowercase
Collection Functions:
atLeast(2, [condition1, condition2, condition3]) # At least N conditions true
exactly(1, [optionA, optionB, optionC]) # Exactly N conditions true
forEach(items, itemCondition) # All items satisfy condition
uniqueBy(collection, ["field1", "field2"]) # Unique by specified fields
minLength(array, 3) # Minimum array length
maxLength(array, 10) # Maximum array length
Conditional Functions:
requiredIf(field, condition) # Field required if condition true
visibleIf(field, condition) # Field visible if condition true
disabledIf(field, condition) # Field disabled if condition true
readonlyIf(field, condition) # Field readonly if condition true
Optional Handling:
ifDefined(optionalField, condition) # Apply condition only if field defined
ifNotNull(field, condition) # Apply condition only if field not null
ifExists(field, condition) # Apply condition only if field exists
withDefault(field, defaultValue, condition) # Use default if field undefined
((age >= 18 && age <= 65) || experience > 10) &&
(department == "Engineering" || department == "Product") &&
!(archived == true || deleted == true)
salary >= ${policy.minSalary} &&
salary <= ${policy.maxSalary} &&
location in ${user.allowedLocations} &&
startDate >= "${session.currentDate}"
contains(upper(department), "ENG") &&
length(trim(description)) > ${config.minDescLength} &&
endsWith(lower(email), "@company.com")
Context variables provide dynamic values that can be resolved at runtime based on different scopes.
User Scope (user.*):
${user.department}, ${user.role}, ${user.level}Session Scope (session.*):
${session.currentDate}, ${session.userId}, ${session.locale}Environment Scope (env.*):
${env.debug}, ${env.features.newUI}, ${env.apiVersion}Global Scope (global.*):
${global.minAge}, ${global.maxFileSize}, ${global.supportedFormats}String Variables:
name == "${user.fullName}"
department == "${user.department}"
Number Variables:
age > ${user.minAge}
salary >= ${policy.baseSalary}
Boolean Variables:
active == ${user.isActive}
debug == ${env.debugMode}
Array Variables:
category in ${user.allowedCategories}
format in ${global.supportedFormats}
Date Variables:
createdDate >= "${session.startDate}"
expirationDate <= "${policy.maxExpirationDate}"
The DSL parser provides detailed error messages for syntax issues:
// Invalid syntax examples and their error messages:
// "age >" -> "Missing operand after comparison operator"
// "age >> 18" -> "Unknown operator '>>'"
// "(age > 18" -> "Unclosed parentheses"
// "unknownFunc(age)" -> "Unknown function 'unknownFunc'"
// Unknown field warnings:
// "unknownField == 'test'" -> "Unknown field: unknownField. Did you mean 'knownField'?"
// Missing context variable warnings:
// "age > ${missing.variable}" -> "Unknown context variable: missing.variable"
// With suggestions: "Did you mean 'existing.variable'?"
// Complex expression warnings:
// For expressions with >50 operators:
// "Expression complexity is high (67 operators). Consider breaking into smaller expressions."
Main exports available from @praxisui/visual-builder (see projects/praxis-visual-builder/src/public-api.ts):
RuleEditorComponent, RuleCanvasComponent, RuleNodeComponent, FieldConditionEditorComponent, ConditionalValidatorEditorComponent, CollectionValidatorEditorComponent, MetadataEditorComponent, DslViewerComponent, JsonViewerComponent, RoundTripTesterComponent, ExportDialogComponent, DslLinterComponent, VisualRuleBuilderComponent, TemplateGalleryComponent, TemplateEditorDialogComponent, TemplatePreviewDialogComponentSpecificationBridgeService, RuleBuilderService, RoundTripValidatorService, ExportIntegrationService, WebhookIntegrationService, RuleTemplateService, RuleValidationService, RuleNodeRegistryService, ContextManagementService, FieldSchemaService, RuleConversionService, DslParsingServiceFieldSchema, RuleBuilderConfig, ArrayFieldSchema, ContextScope, ContextEntry, ContextValue, SpecificationContextualConfigVisualBuilderError, VBValidationError, ConversionError, RegistryError, DslError, ContextError, ConfigurationError, InternalError, ErrorCategory, ErrorSeverity, ErrorHandler, globalErrorHandler, createError, ErrorInfo, ErrorStatistics@Input() expression: string = ''; // Current DSL expression
@Input() fieldSchemas: FieldSchema[] = []; // Available fields for autocomplete
@Input() contextVariables: ContextVariable[] = []; // Available context variables
@Input() functionRegistry?: FunctionRegistry; // Custom function registry
@Input() validationConfig?: ValidationConfig; // Validation configuration
@Input() editorOptions?: EditorOptions; // Editor display options
@Output() expressionChange = new EventEmitter<string>(); // Expression changes
@Output() validationChange = new EventEmitter<ExpressionValidationResult>(); // Validation updates
@Output() suggestionRequest = new EventEmitter<SuggestionContext>(); // Autocomplete requests
@Output() focusChange = new EventEmitter<boolean>(); // Focus state changes
// Get autocomplete suggestions
getSuggestions(text: string, position: number): Promise<DslSuggestion[]>
// Validate current expression
validateExpression(): Promise<ExpressionValidationResult>
// Insert text at cursor position
insertTextAtCursor(text: string): void
// Format expression
formatExpression(): void
// Clear expression
clearExpression(): void
@Input() contextVariables: EnhancedContextVariable[] = []; // Current variables
@Input() allowedScopes: ContextScope[] = []; // Allowed variable scopes
@Input() readOnly: boolean = false; // Read-only mode
@Input() showCategories: boolean = true; // Show category grouping
@Input() allowImportExport: boolean = true; // Enable import/export
@Output() contextVariablesChange = new EventEmitter<EnhancedContextVariable[]>(); // Variables updated
@Output() variableAdd = new EventEmitter<EnhancedContextVariable>(); // Variable added
@Output() variableUpdate = new EventEmitter<EnhancedContextVariable>(); // Variable updated
@Output() variableDelete = new EventEmitter<string>(); // Variable deleted
@Output() importComplete = new EventEmitter<ImportResult>(); // Import completed
// Add new variable
addVariable(variable: Partial<EnhancedContextVariable>): void
// Update existing variable
updateVariable(id: string, updates: Partial<EnhancedContextVariable>): void
// Delete variable
deleteVariable(id: string): void
// Get variables by scope
getVariablesByScope(scope: string): EnhancedContextVariable[]
// Export variables
exportVariables(format: 'json' | 'csv'): string
// Import variables
importVariables(data: string, format: 'json' | 'csv'): Promise<ImportResult>
// Validate variable name
isValidVariableName(name: string): boolean
// Parse DSL expression to specification
parseDslExpression<T>(expression: string, config?: DslParsingConfig): DslParsingResult<T>
// Validate expression round-trip integrity
validateExpressionRoundTrip<T>(expression: string, config?: DslParsingConfig): RoundTripResult
// Create contextual specification
createContextualSpecification<T>(template: string, config?: ContextualConfig): ContextualSpecification<T>
// Resolve context tokens in template
resolveContextTokens(template: string, variables: ContextVariable[]): string
// Extract context tokens from template
extractContextTokens(template: string): string[]
// Validate context tokens
validateContextTokens(template: string, variables: ContextVariable[]): ValidationIssue[]
// Convert rule node to specification
ruleNodeToSpecification<T>(node: RuleNode): Specification<T>
// Convert specification to rule node
specificationToRuleNode<T>(spec: Specification<T>): RuleNode
// Export rule node to DSL
exportToDsl<T>(node: RuleNode, options?: ExportOptions): string
// Validate round-trip through rule nodes
validateRoundTrip<T>(node: RuleNode): RoundTripValidationResult
// Update context provider
updateContextProvider(provider: ContextProvider): void
// Get current context provider
getContextProvider(): ContextProvider | undefined
import { FunctionRegistry } from '@praxisui/specification';
// Create custom function registry
const customRegistry = new FunctionRegistry();
// Register custom functions
customRegistry.register('isEmail', {
name: 'isEmail',
arity: 1,
implementation: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
description: 'Validates email format'
});
customRegistry.register('dateAdd', {
name: 'dateAdd',
arity: 3,
implementation: (date: Date, amount: number, unit: string) => {
// Implementation for date arithmetic
},
description: 'Adds time to a date'
});
// Use in expression editor
<praxis-expression-editor
[functionRegistry]="customRegistry"
expression="isEmail(userEmail) && dateAdd(startDate, 30, 'days') > now()">
</praxis-expression-editor>
import { ContextProvider } from "@praxisui/specification";
class DatabaseContextProvider implements ContextProvider {
private cache = new Map<string, any>();
async getValue(key: string): Promise<any> {
if (this.cache.has(key)) {
return this.cache.get(key);
}
// Fetch from database or API
const value = await this.fetchFromDatabase(key);
this.cache.set(key, value);
return value;
}
hasValue(key: string): boolean {
return this.cache.has(key) || this.isValidKey(key);
}
setValue(key: string, value: any): void {
this.cache.set(key, value);
}
// ... other methods
}
// Use custom provider
const provider = new DatabaseContextProvider();
this.bridgeService.updateContextProvider(provider);
@Component({
template: `
<form [formGroup]="ruleForm">
<!-- Visual Rule Builder -->
<praxis-expression-editor formControlName="expression" [fieldSchemas]="formFieldSchemas" [contextVariables]="availableVariables"> </praxis-expression-editor>
<!-- Context Variable Management -->
<praxis-context-variable-manager [(contextVariables)]="availableVariables" [allowedScopes]="['user', 'session']"> </praxis-context-variable-manager>
<!-- Generated Rule Preview -->
<pre>{{ generatedRule | json }}</pre>
</form>
`,
})
export class RuleBuilderFormComponent {
ruleForm = this.fb.group({
expression: ["", Validators.required],
metadata: this.fb.group({
name: ["", Validators.required],
description: [""],
priority: [1],
}),
});
get generatedRule() {
const expression = this.ruleForm.get("expression")?.value;
if (!expression) return null;
const parseResult = this.bridgeService.parseDslExpression(expression, {
knownFields: this.formFieldSchemas.map((f) => f.name),
});
return parseResult.success ? parseResult.specification?.toJSON() : null;
}
}
Field Schema Management:
// ✅ Good: Reuse field schema objects
const fieldSchemas = useMemo(() => fields.map((f) => ({ name: f.name, type: f.type, label: f.label })), [fields]);
// ❌ Avoid: Creating new objects on every render
const fieldSchemas = fields.map((f) => ({ name: f.name, type: f.type }));
Expression Validation:
// ✅ Good: Debounce validation
const debouncedValidation = useMemo(
() => debounce(validateExpression, 300),
[]
);
// ❌ Avoid: Validating on every keystroke
onChange={(expr) => validateExpression(expr)}
To build the library, run:
ng build praxis-visual-builder
This command will compile your project, and the build artifacts will be placed in the dist/ directory.
Once the project is built, you can publish your library by following these steps:
Navigate to the dist directory:
cd dist/praxis-visual-builder
Run the npm publish command to publish your library to the npm registry:
npm publish
To execute unit tests with the Karma test runner, use the following command:
ng test praxis-visual-builder
Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
Apache-2.0 – see the LICENSE packaged with this library or the repository root.
FAQs
Visual rule and expression builder for Praxis UI with mini-DSL support, validation and context variables.
We found that @praxisui/visual-builder demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.