UNPKG

qnce-engine

Version:

Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization

195 lines 6.92 kB
"use strict"; // QNCE Condition Evaluator - Sprint 3.4 // Parses and evaluates conditional expressions for choice visibility Object.defineProperty(exports, "__esModule", { value: true }); exports.conditionEvaluator = exports.ConditionEvaluator = exports.ConditionEvaluationError = void 0; /** * Error thrown when condition evaluation fails */ class ConditionEvaluationError extends Error { expression; cause; constructor(message, expression, cause) { super(message); this.expression = expression; this.cause = cause; this.name = 'ConditionEvaluationError'; } } exports.ConditionEvaluationError = ConditionEvaluationError; /** * Built-in expression operators */ const OPERATORS = { '>=': (a, b) => a >= b, '<=': (a, b) => a <= b, '>': (a, b) => a > b, '<': (a, b) => a < b, '==': (a, b) => a == b, '===': (a, b) => a === b, '!=': (a, b) => a != b, '!==': (a, b) => a !== b, '&&': (a, b) => a && b, '||': (a, b) => a || b, }; /** * Condition evaluator service for parsing and executing conditional expressions */ class ConditionEvaluator { customEvaluator; // Cache compiled functions for better performance functionCache = new Map(); maxCacheSize = 100; /** * Set a custom evaluator function for handling complex conditions */ setCustomEvaluator(evaluator) { this.customEvaluator = evaluator; } /** * Clear the custom evaluator */ clearCustomEvaluator() { this.customEvaluator = undefined; } /** * Evaluate a condition expression against the provided context */ evaluate(expression, context) { try { // If custom evaluator is set, use it first if (this.customEvaluator) { return this.customEvaluator(expression, context); } // Use built-in evaluator return this.evaluateBuiltIn(expression, context); } catch (error) { throw new ConditionEvaluationError(`Failed to evaluate condition: ${expression}`, expression, error instanceof Error ? error : new Error(String(error))); } } /** * Built-in expression evaluator with support for common patterns */ evaluateBuiltIn(expression, context) { // Handle empty expressions if (!expression || expression.trim() === '') { throw new ConditionEvaluationError('Empty or whitespace-only condition expression', expression); } // Sanitize and prepare expression const sanitizedExpression = this.sanitizeExpression(expression); // Handle simple cases first if (sanitizedExpression === 'true') return true; if (sanitizedExpression === 'false') return false; // Create safe evaluation context const evalContext = this.createEvaluationContext(context); // Check function cache for performance let func = this.functionCache.get(sanitizedExpression); if (!func) { try { // Use Function constructor for safe evaluation (better than eval) func = new Function('flags', 'state', 'timestamp', 'customData', ` "use strict"; return ${sanitizedExpression}; `); // Cache the function for future use if (this.functionCache.size >= this.maxCacheSize) { // Remove oldest entry when cache is full const firstKey = this.functionCache.keys().next().value; if (firstKey) { this.functionCache.delete(firstKey); } } this.functionCache.set(sanitizedExpression, func); } catch (error) { throw new ConditionEvaluationError(`Invalid expression syntax: ${expression}`, expression, error instanceof Error ? error : new Error(String(error))); } } try { return !!func(evalContext.flags, evalContext.state, evalContext.timestamp, evalContext.customData); } catch (error) { throw new ConditionEvaluationError(`Runtime error evaluating: ${expression}`, expression, error instanceof Error ? error : new Error(String(error))); } } /** * Sanitize expression to prevent code injection */ sanitizeExpression(expression) { // Remove potentially dangerous patterns const dangerous = [ /\beval\b/g, /\bFunction\b/g, /\bconstructor\b/g, /\bprototype\b/g, /\b__proto__\b/g, /\bimport\b/g, /\brequire\b/g, /\bprocess\b/g, /\bglobal\b/g, /\bwindow\b/g, /\bdocument\b/g, ]; let sanitized = expression.trim(); for (const pattern of dangerous) { if (pattern.test(sanitized)) { throw new ConditionEvaluationError(`Potentially unsafe expression detected: ${expression}`, expression); } } return sanitized; } /** * Create a safe evaluation context from the condition context */ createEvaluationContext(context) { return { flags: { ...context.state.flags }, state: { currentNodeId: context.state.currentNodeId, flags: { ...context.state.flags }, history: [...context.state.history], }, timestamp: context.timestamp, customData: context.customData ? { ...context.customData } : {}, }; } /** * Validate that an expression is syntactically correct without evaluating it */ validateExpression(expression) { try { this.sanitizeExpression(expression); // Try to create the function without executing it new Function('flags', 'state', 'timestamp', 'customData', ` "use strict"; return ${expression}; `); return { valid: true }; } catch (error) { return { valid: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Get list of flag names referenced in an expression */ getReferencedFlags(expression) { const flagPattern = /flags\.([a-zA-Z_$][a-zA-Z0-9_$]*)/g; const matches = []; let match; while ((match = flagPattern.exec(expression)) !== null) { matches.push(match[1]); } return Array.from(new Set(matches)); // Remove duplicates } } exports.ConditionEvaluator = ConditionEvaluator; // Create a global instance for the engine to use exports.conditionEvaluator = new ConditionEvaluator(); //# sourceMappingURL=condition.js.map