Advanced Usage

Advanced patterns, performance optimization, error handling, and extending JEXL Extended

Advanced Usage

This guide covers advanced techniques for using JEXL Extended, including performance optimization, custom extensions, error handling strategies, and integration patterns for complex applications.

Performance Optimization

Expression Compilation and Caching

For applications that evaluate the same expressions repeatedly, compile and cache them:

class ExpressionCache {
  private cache = new Map<string, any>();
  private jexl: any;

  constructor(jexl: any) {
    this.jexl = jexl;
  }

  compile(key: string, expression: string) {
    if (!this.cache.has(key)) {
      this.cache.set(key, this.jexl.compile(expression));
    }
    return this.cache.get(key);
  }

  eval(key: string, context: any) {
    const compiled = this.cache.get(key);
    if (!compiled) {
      throw new Error(`Expression '${key}' not found in cache`);
    }
    return compiled.evalSync(context);
  }

  invalidate(key?: string) {
    if (key) {
      this.cache.delete(key);
    } else {
      this.cache.clear();
    }
  }

  getStats() {
    return {
      size: this.cache.size,
      keys: Array.from(this.cache.keys())
    };
  }
}

// Usage
import jexl from 'jexl-extended';
const expressionCache = new ExpressionCache(jexl);

// Compile frequently used expressions
expressionCache.compile('userFullName', 'user.firstName + " " + user.lastName');
expressionCache.compile('eligibilityCheck', 'user.age >= 18 && user.verified && user.status == "active"');
expressionCache.compile('salesReport', 'orders | sum("value.amount")');

// Fast repeated evaluation
const users = getUsersFromDatabase();
users.forEach(user => {
  const fullName = expressionCache.eval('userFullName', { user });
  const isEligible = expressionCache.eval('eligibilityCheck', { user });
  console.log(`${fullName}: ${isEligible ? 'Eligible' : 'Not eligible'}`);
});

Context Optimization

Optimize context creation for large datasets:

class OptimizedContext {
  private baseContext: any;
  private computedCache = new Map<string, any>();
  private accessLog = new Set<string>();

  constructor(baseContext: any) {
    this.baseContext = baseContext;
  }

  // Lazy computation of expensive operations
  get(key: string): any {
    this.accessLog.add(key);

    if (this.computedCache.has(key)) {
      return this.computedCache.get(key);
    }

    const value = this.computeValue(key);
    this.computedCache.set(key, value);
    return value;
  }

  private computeValue(key: string): any {
    switch (key) {
      case 'totalUsers':
        return this.baseContext.users?.length || 0;
      
      case 'activeUsers':
        return this.baseContext.users?.filter((u: any) => u.active).length || 0;
      
      case 'totalRevenue':
        return this.baseContext.orders?.reduce((sum: number, order: any) => sum + order.amount, 0) || 0;
      
      case 'averageOrderValue':
        const orders = this.baseContext.orders || [];
        return orders.length > 0 ? this.get('totalRevenue') / orders.length : 0;
      
      default:
        return this.baseContext[key];
    }
  }

  // Get context with only accessed properties computed
  getOptimizedContext(): any {
    const context = { ...this.baseContext };
    
    for (const key of this.accessLog) {
      if (this.computedCache.has(key)) {
        context[key] = this.computedCache.get(key);
      }
    }

    return context;
  }

  // Performance metrics
  getMetrics() {
    return {
      accessedKeys: Array.from(this.accessLog),
      computedKeys: Array.from(this.computedCache.keys()),
      cacheHitRatio: this.accessLog.size > 0 ? this.computedCache.size / this.accessLog.size : 0
    };
  }
}

// Usage
function processLargeDataset(rawData: any) {
  const optimizedContext = new OptimizedContext(rawData);
  
  const expressions = [
    'totalUsers > 1000 ? "Large" : "Small"',
    'activeUsers / totalUsers * 100',
    'averageOrderValue > 100 ? "Premium" : "Standard"'
  ];

  const results = expressions.map(expr => 
    jexl.evalSync(expr, optimizedContext.getOptimizedContext())
  );

  console.log('Metrics:', optimizedContext.getMetrics());
  return results;
}

Batch Processing

Process multiple expressions efficiently:

class BatchProcessor {
  private jexl: any;
  private compiledExpressions = new Map<string, any>();

  constructor(jexl: any) {
    this.jexl = jexl;
  }

  // Prepare expressions for batch processing
  prepare(expressions: { [key: string]: string }) {
    for (const [key, expression] of Object.entries(expressions)) {
      this.compiledExpressions.set(key, this.jexl.compile(expression));
    }
  }

  // Process batch with shared context
  processBatch(contexts: any[]): any[] {
    return contexts.map(context => {
      const result: any = {};
      
      for (const [key, compiled] of this.compiledExpressions) {
        try {
          result[key] = compiled.evalSync(context);
        } catch (error) {
          result[key] = { error: error.message };
        }
      }
      
      return result;
    });
  }

  // Process with streaming for large datasets
  async processStream(contexts: any[], onBatch?: (results: any[]) => void, batchSize = 100): Promise<any[]> {
    const allResults: any[] = [];
    
    for (let i = 0; i < contexts.length; i += batchSize) {
      const batch = contexts.slice(i, i + batchSize);
      const batchResults = this.processBatch(batch);
      
      allResults.push(...batchResults);
      
      if (onBatch) {
        onBatch(batchResults);
      }
      
      // Allow event loop to process other tasks
      await new Promise(resolve => setTimeout(resolve, 0));
    }
    
    return allResults;
  }
}

// Usage
const processor = new BatchProcessor(jexl);

processor.prepare({
  fullName: 'firstName + " " + lastName',
  isEligible: 'age >= 18 && verified',
  scoreGrade: 'score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F"',
  monthsActive: '(now() - joinDate | dateTimeToMillis) / (30 * 24 * 60 * 60 * 1000) | floor'
});

const users = getLargeUserDataset(); // 10,000+ users

// Process in batches with progress tracking
processor.processStream(users, (batchResults) => {
  console.log(`Processed batch of ${batchResults.length} users`);
}, 500);

Custom Extensions

Adding Transform Functions

Extend JEXL with custom transform functions:

import jexl from 'jexl-extended';

// Add custom transforms
jexl.addTransform('slugify', (value: string) => {
  return value
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');
});

jexl.addTransform('truncate', (value: string, length: number = 50, suffix: string = '...') => {
  if (!value || value.length <= length) return value;
  return value.substring(0, length) + suffix;
});

jexl.addTransform('currency', (value: number, currency: string = 'USD', locale: string = 'en-US') => {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: currency
  }).format(value);
});

jexl.addTransform('highlight', (text: string, searchTerm: string, highlightClass: string = 'highlight') => {
  if (!searchTerm) return text;
  const regex = new RegExp(`(${searchTerm})`, 'gi');
  return text.replace(regex, `<span class="${highlightClass}">$1</span>`);
});

// Usage
const title = "Hello World Example";
const slugified = jexl.evalSync('title | slugify', { title }); // "hello-world-example"

const longText = "This is a very long piece of text that needs truncation";
const truncated = jexl.evalSync('text | truncate(20)', { text: longText }); // "This is a very long..."

const price = 1234.56;
const formatted = jexl.evalSync('price | currency("EUR", "de-DE")', { price }); // "1.234,56 €"

Custom Functions

Add custom functions for complex operations:

// Add custom functions
jexl.addFunction('distance', (lat1: number, lon1: number, lat2: number, lon2: number) => {
  const R = 6371; // Earth's radius in kilometers
  const dLat = (lat2 - lat1) * Math.PI / 180;
  const dLon = (lon2 - lon1) * Math.PI / 180;
  const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
            Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
            Math.sin(dLon/2) * Math.sin(dLon/2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  return R * c;
});

jexl.addFunction('creditScore', (income: number, debt: number, paymentHistory: number) => {
  // Simplified credit score calculation
  const debtToIncomeRatio = debt / income;
  const baseScore = 300;
  const incomeBonus = Math.min(income / 1000, 200);
  const debtPenalty = debtToIncomeRatio * 100;
  const historyBonus = paymentHistory * 50;
  
  return Math.max(300, Math.min(850, baseScore + incomeBonus - debtPenalty + historyBonus));
});

jexl.addFunction('validateEmail', (email: string) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
});

// Usage
const userLocation = { lat: 40.7128, lon: -74.0060 }; // New York
const storeLocation = { lat: 34.0522, lon: -118.2437 }; // Los Angeles
const distanceKm = jexl.evalSync(
  'distance(user.lat, user.lon, store.lat, store.lon)',
  { user: userLocation, store: storeLocation }
); // ~3944 km

const financialData = { income: 75000, debt: 25000, paymentHistory: 0.95 };
const score = jexl.evalSync(
  'creditScore(income, debt, paymentHistory)',
  financialData
); // Credit score calculation

Advanced Error Handling

Expression Validation Pipeline

interface ValidationResult {
  valid: boolean;
  errors: string[];
  warnings: string[];
  suggestions: string[];
}

class ExpressionValidator {
  private jexl: any;
  private customValidators: Array<(expr: string) => ValidationResult> = [];

  constructor(jexl: any) {
    this.jexl = jexl;
    this.setupDefaultValidators();
  }

  private setupDefaultValidators() {
    // Syntax validation
    this.addValidator((expression) => {
      try {
        this.jexl.compile(expression);
        return { valid: true, errors: [], warnings: [], suggestions: [] };
      } catch (error) {
        return {
          valid: false,
          errors: [`Syntax error: ${error.message}`],
          warnings: [],
          suggestions: ['Check parentheses and quotes', 'Verify function names']
        };
      }
    });

    // Performance validation
    this.addValidator((expression) => {
      const warnings: string[] = [];
      const suggestions: string[] = [];
      
      // Check for potentially expensive operations
      if (expression.includes('| map(') && expression.includes('| filter(')) {
        const mapIndex = expression.indexOf('| map(');
        const filterIndex = expression.indexOf('| filter(');
        
        if (mapIndex < filterIndex) {
          warnings.push('Consider filtering before mapping for better performance');
          suggestions.push('Move filter operations before map operations when possible');
        }
      }

      // Check for nested function calls
      const nestedFunctionRegex = /\w+\([^()]*\w+\([^()]*\)[^()]*\)/g;
      if (nestedFunctionRegex.test(expression)) {
        suggestions.push('Consider breaking down complex nested functions for readability');
      }

      return { valid: true, errors: [], warnings, suggestions };
    });
  }

  addValidator(validator: (expr: string) => ValidationResult) {
    this.customValidators.push(validator);
  }

  validate(expression: string): ValidationResult {
    const results = this.customValidators.map(validator => validator(expression));
    
    return {
      valid: results.every(r => r.valid),
      errors: results.flatMap(r => r.errors),
      warnings: results.flatMap(r => r.warnings),
      suggestions: results.flatMap(r => r.suggestions)
    };
  }
}

// Usage
const validator = new ExpressionValidator(jexl);

// Add custom business logic validator
validator.addValidator((expression) => {
  const warnings: string[] = [];
  
  // Check for potentially unsafe operations
  if (expression.includes('eval(')) {
    return {
      valid: false,
      errors: ['Use of eval() function is not allowed for security reasons'],
      warnings: [],
      suggestions: ['Use other transformation functions instead']
    };
  }

  // Check for deprecated functions
  if (expression.includes('oldFunction(')) {
    warnings.push('oldFunction() is deprecated, use newFunction() instead');
  }

  return { valid: true, errors: [], warnings, suggestions: [] };
});

const validationResult = validator.validate('users | map("value.name") | filter("value.length > 0")');
console.log(validationResult);

Graceful Error Recovery

class SafeEvaluator {
  private jexl: any;
  private fallbackStrategies = new Map<string, any>();

  constructor(jexl: any) {
    this.jexl = jexl;
    this.setupDefaultFallbacks();
  }

  private setupDefaultFallbacks() {
    // Fallback for undefined properties
    this.fallbackStrategies.set('undefined_property', {
      detect: (error: Error) => error.message.includes('Cannot read property'),
      handle: (expression: string, context: any, error: Error) => {
        console.warn(`Property access failed: ${error.message}`);
        return null;
      }
    });

    // Fallback for type errors
    this.fallbackStrategies.set('type_error', {
      detect: (error: Error) => error.message.includes('is not a function') || error.message.includes('Cannot read property'),
      handle: (expression: string, context: any, error: Error) => {
        console.warn(`Type error in expression: ${error.message}`);
        return undefined;
      }
    });
  }

  evalWithRecovery(expression: string, context: any, options: {
    defaultValue?: any;
    maxRetries?: number;
    onError?: (error: Error, attempt: number) => void;
  } = {}) {
    const { defaultValue = null, maxRetries = 3, onError } = options;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return this.jexl.evalSync(expression, context);
      } catch (error) {
        if (onError) {
          onError(error, attempt);
        }

        // Try fallback strategies
        for (const [name, strategy] of this.fallbackStrategies) {
          if (strategy.detect(error)) {
            try {
              return strategy.handle(expression, context, error);
            } catch (fallbackError) {
              console.warn(`Fallback strategy '${name}' failed:`, fallbackError.message);
            }
          }
        }

        // If this is the last attempt, return default value
        if (attempt === maxRetries) {
          console.error(`Expression evaluation failed after ${maxRetries} attempts:`, error.message);
          return defaultValue;
        }
      }
    }

    return defaultValue;
  }

  // Batch evaluation with individual error handling
  evalBatch(expressions: Array<{ key: string; expression: string; context: any; defaultValue?: any }>) {
    const results: { [key: string]: any } = {};
    const errors: { [key: string]: string } = {};

    for (const { key, expression, context, defaultValue } of expressions) {
      try {
        results[key] = this.evalWithRecovery(expression, context, { defaultValue });
      } catch (error) {
        errors[key] = error.message;
        results[key] = defaultValue || null;
      }
    }

    return { results, errors };
  }
}

// Usage
const safeEvaluator = new SafeEvaluator(jexl);

const result = safeEvaluator.evalWithRecovery(
  'user.profile.preferences.theme || "light"',
  { user: {} }, // Missing nested properties
  {
    defaultValue: 'light',
    onError: (error, attempt) => {
      console.log(`Attempt ${attempt} failed: ${error.message}`);
    }
  }
);

Integration Patterns

Plugin Architecture

interface JexlPlugin {
  name: string;
  version: string;
  install(jexl: any): void;
  uninstall?(jexl: any): void;
}

class PluginManager {
  private jexl: any;
  private installedPlugins = new Map<string, JexlPlugin>();

  constructor(jexl: any) {
    this.jexl = jexl;
  }

  install(plugin: JexlPlugin) {
    if (this.installedPlugins.has(plugin.name)) {
      throw new Error(`Plugin '${plugin.name}' is already installed`);
    }

    try {
      plugin.install(this.jexl);
      this.installedPlugins.set(plugin.name, plugin);
      console.log(`Plugin '${plugin.name}' v${plugin.version} installed successfully`);
    } catch (error) {
      console.error(`Failed to install plugin '${plugin.name}':`, error);
      throw error;
    }
  }

  uninstall(pluginName: string) {
    const plugin = this.installedPlugins.get(pluginName);
    if (!plugin) {
      throw new Error(`Plugin '${pluginName}' is not installed`);
    }

    try {
      if (plugin.uninstall) {
        plugin.uninstall(this.jexl);
      }
      this.installedPlugins.delete(pluginName);
      console.log(`Plugin '${pluginName}' uninstalled successfully`);
    } catch (error) {
      console.error(`Failed to uninstall plugin '${pluginName}':`, error);
    }
  }

  getInstalledPlugins() {
    return Array.from(this.installedPlugins.values());
  }
}

// Example plugins
const mathPlugin: JexlPlugin = {
  name: 'advanced-math',
  version: '1.0.0',
  install: (jexl) => {
    jexl.addFunction('factorial', (n: number) => {
      if (n <= 1) return 1;
      return n * jexl.evalSync('factorial(' + (n - 1) + ')');
    });

    jexl.addFunction('fibonacci', (n: number) => {
      if (n <= 1) return n;
      return jexl.evalSync(`fibonacci(${n - 1}) + fibonacci(${n - 2})`);
    });

    jexl.addTransform('toRadians', (degrees: number) => degrees * Math.PI / 180);
    jexl.addTransform('toDegrees', (radians: number) => radians * 180 / Math.PI);
  }
};

const validationPlugin: JexlPlugin = {
  name: 'validation-helpers',
  version: '1.0.0',
  install: (jexl) => {
    jexl.addFunction('isEmail', (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email));
    jexl.addFunction('isPhone', (phone: string) => /^\+?[\d\s\-\(\)]+$/.test(phone));
    jexl.addFunction('isUrl', (url: string) => {
      try {
        new URL(url);
        return true;
      } catch {
        return false;
      }
    });

    jexl.addTransform('sanitizeHtml', (html: string) => {
      return html.replace(/<[^>]*>/g, '');
    });
  }
};

// Usage
const pluginManager = new PluginManager(jexl);
pluginManager.install(mathPlugin);
pluginManager.install(validationPlugin);

// Use plugin functions
const factorialResult = jexl.evalSync('factorial(5)'); // 120
const emailValid = jexl.evalSync('isEmail("user@example.com")'); // true

Middleware System

interface MiddlewareContext {
  expression: string;
  context: any;
  result?: any;
  error?: Error;
  metadata: { [key: string]: any };
}

type Middleware = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;

class MiddlewareEngine {
  private middlewares: Middleware[] = [];
  private jexl: any;

  constructor(jexl: any) {
    this.jexl = jexl;
  }

  use(middleware: Middleware) {
    this.middlewares.push(middleware);
  }

  async eval(expression: string, context: any = {}, metadata: any = {}) {
    const ctx: MiddlewareContext = {
      expression,
      context,
      metadata: { startTime: Date.now(), ...metadata }
    };

    const executeMiddleware = async (index: number): Promise<void> => {
      if (index >= this.middlewares.length) {
        // Execute the actual evaluation
        try {
          ctx.result = this.jexl.evalSync(ctx.expression, ctx.context);
        } catch (error) {
          ctx.error = error;
        }
        return;
      }

      const middleware = this.middlewares[index];
      await middleware(ctx, () => executeMiddleware(index + 1));
    };

    await executeMiddleware(0);

    if (ctx.error) {
      throw ctx.error;
    }

    return ctx.result;
  }
}

// Example middlewares
const loggingMiddleware: Middleware = async (ctx, next) => {
  console.log(`Evaluating: ${ctx.expression}`);
  const start = Date.now();
  
  await next();
  
  const duration = Date.now() - start;
  console.log(`Completed in ${duration}ms`);
};

const cachingMiddleware: Middleware = async (ctx, next) => {
  const cache = new Map<string, any>();
  const key = `${ctx.expression}:${JSON.stringify(ctx.context)}`;
  
  if (cache.has(key)) {
    ctx.result = cache.get(key);
    return;
  }
  
  await next();
  
  if (!ctx.error) {
    cache.set(key, ctx.result);
  }
};

const securityMiddleware: Middleware = async (ctx, next) => {
  // Block potentially dangerous expressions
  const dangerousPatterns = [/eval\s*\(/, /function\s*\(/, /constructor/];
  
  for (const pattern of dangerousPatterns) {
    if (pattern.test(ctx.expression)) {
      ctx.error = new Error('Expression contains potentially dangerous code');
      return;
    }
  }
  
  await next();
};

// Usage
const engine = new MiddlewareEngine(jexl);
engine.use(securityMiddleware);
engine.use(loggingMiddleware);
engine.use(cachingMiddleware);

const result = await engine.eval('users | filter("value.active") | length', { users: [...] });

Testing Strategies

Expression Testing Framework

interface TestCase {
  name: string;
  expression: string;
  context: any;
  expected: any;
  shouldThrow?: boolean;
  errorMessage?: string;
}

class ExpressionTester {
  private jexl: any;
  private results: Array<{ name: string; passed: boolean; error?: string }> = [];

  constructor(jexl: any) {
    this.jexl = jexl;
  }

  test(testCase: TestCase) {
    try {
      const result = this.jexl.evalSync(testCase.expression, testCase.context);
      
      if (testCase.shouldThrow) {
        this.results.push({
          name: testCase.name,
          passed: false,
          error: 'Expected expression to throw an error, but it succeeded'
        });
        return;
      }

      const passed = this.deepEqual(result, testCase.expected);
      this.results.push({
        name: testCase.name,
        passed,
        error: passed ? undefined : `Expected ${JSON.stringify(testCase.expected)}, got ${JSON.stringify(result)}`
      });

    } catch (error) {
      if (testCase.shouldThrow) {
        const messageMatches = !testCase.errorMessage || error.message.includes(testCase.errorMessage);
        this.results.push({
          name: testCase.name,
          passed: messageMatches,
          error: messageMatches ? undefined : `Expected error message to contain "${testCase.errorMessage}", got "${error.message}"`
        });
      } else {
        this.results.push({
          name: testCase.name,
          passed: false,
          error: `Unexpected error: ${error.message}`
        });
      }
    }
  }

  runSuite(testCases: TestCase[]) {
    this.results = [];
    testCases.forEach(testCase => this.test(testCase));
    return this.getReport();
  }

  private deepEqual(a: any, b: any): boolean {
    return JSON.stringify(a) === JSON.stringify(b);
  }

  getReport() {
    const passed = this.results.filter(r => r.passed).length;
    const total = this.results.length;
    
    return {
      passed,
      failed: total - passed,
      total,
      success: passed === total,
      results: this.results
    };
  }
}

// Usage
const tester = new ExpressionTester(jexl);

const testSuite: TestCase[] = [
  {
    name: 'Basic arithmetic',
    expression: '2 + 3 * 4',
    context: {},
    expected: 14
  },
  {
    name: 'String operations',
    expression: 'name | uppercase | split(" ") | join("-")',
    context: { name: "John Doe" },
    expected: "JOHN-DOE"
  },
  {
    name: 'Array filtering',
    expression: 'numbers | filter("value > 5") | length',
    context: { numbers: [1, 6, 3, 8, 2, 9] },
    expected: 3
  },
  {
    name: 'Error handling',
    expression: 'user.invalid.property',
    context: { user: {} },
    shouldThrow: true,
    errorMessage: 'Cannot read property'
  }
];

const report = tester.runSuite(testSuite);
console.log(`Tests: ${report.passed}/${report.total} passed`);
report.results.forEach(result => {
  if (!result.passed) {
    console.error(`❌ ${result.name}: ${result.error}`);
  } else {
    console.log(`✅ ${result.name}`);
  }
});

Production Considerations

Monitoring and Metrics

class ExpressionMetrics {
  private metrics = {
    evaluations: 0,
    errors: 0,
    totalTime: 0,
    expressionCounts: new Map<string, number>(),
    errorTypes: new Map<string, number>()
  };

  recordEvaluation(expression: string, duration: number, error?: Error) {
    this.metrics.evaluations++;
    this.metrics.totalTime += duration;
    
    const count = this.metrics.expressionCounts.get(expression) || 0;
    this.metrics.expressionCounts.set(expression, count + 1);
    
    if (error) {
      this.metrics.errors++;
      const errorType = error.constructor.name;
      const errorCount = this.metrics.errorTypes.get(errorType) || 0;
      this.metrics.errorTypes.set(errorType, errorCount + 1);
    }
  }

  getMetrics() {
    return {
      ...this.metrics,
      averageTime: this.metrics.evaluations > 0 ? this.metrics.totalTime / this.metrics.evaluations : 0,
      errorRate: this.metrics.evaluations > 0 ? this.metrics.errors / this.metrics.evaluations : 0,
      topExpressions: Array.from(this.metrics.expressionCounts.entries())
        .sort(([,a], [,b]) => b - a)
        .slice(0, 10),
      topErrors: Array.from(this.metrics.errorTypes.entries())
        .sort(([,a], [,b]) => b - a)
    };
  }

  reset() {
    this.metrics = {
      evaluations: 0,
      errors: 0,
      totalTime: 0,
      expressionCounts: new Map(),
      errorTypes: new Map()
    };
  }
}

// Wrapper with metrics
class MonitoredJexl {
  private jexl: any;
  private metrics = new ExpressionMetrics();

  constructor(jexl: any) {
    this.jexl = jexl;
  }

  evalSync(expression: string, context: any = {}) {
    const start = Date.now();
    let error: Error | undefined;
    
    try {
      const result = this.jexl.evalSync(expression, context);
      return result;
    } catch (e) {
      error = e;
      throw e;
    } finally {
      const duration = Date.now() - start;
      this.metrics.recordEvaluation(expression, duration, error);
    }
  }

  getMetrics() {
    return this.metrics.getMetrics();
  }

  resetMetrics() {
    this.metrics.reset();
  }
}

Advanced JEXL usage opens up powerful possibilities for building sophisticated expression evaluation systems. These patterns help you build maintainable, performant, and secure applications that leverage the full power of JEXL Extended.

Next Steps

  • Explore Examples - Check out real-world examples in the repository
  • Performance Testing - Benchmark your expressions with your data
  • Security Review - Validate your custom extensions for security
  • Community - Share your custom plugins and extensions

The advanced patterns in this guide provide a foundation for building enterprise-grade applications with JEXL Extended.

Cookie Notice

We use cookies to enhance your browsing experience.