rule-based-chat 1.0.0-beta.0

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.
Files changed (35) hide show
  1. package/dist/index.cjs +2 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.mjs +2 -0
  4. package/dist/index.mjs.map +1 -0
  5. package/dist/index.umd.js +2 -0
  6. package/dist/index.umd.js.map +1 -0
  7. package/dist/types/conversation/ConversationManager.d.ts +82 -0
  8. package/dist/types/conversation/ConversationManager.d.ts.map +1 -0
  9. package/dist/types/conversation/index.d.ts +2 -0
  10. package/dist/types/conversation/index.d.ts.map +1 -0
  11. package/dist/types/engine/ChatEngine.d.ts +185 -0
  12. package/dist/types/engine/ChatEngine.d.ts.map +1 -0
  13. package/dist/types/engine/ConditionEvaluator.d.ts +106 -0
  14. package/dist/types/engine/ConditionEvaluator.d.ts.map +1 -0
  15. package/dist/types/engine/EventEmitter.d.ts +62 -0
  16. package/dist/types/engine/EventEmitter.d.ts.map +1 -0
  17. package/dist/types/engine/TemplateEngine.d.ts +42 -0
  18. package/dist/types/engine/TemplateEngine.d.ts.map +1 -0
  19. package/dist/types/engine/index.d.ts +5 -0
  20. package/dist/types/engine/index.d.ts.map +1 -0
  21. package/dist/types/index.d.ts +9 -0
  22. package/dist/types/index.d.ts.map +1 -0
  23. package/dist/types/rules/RuleProcessor.d.ts +118 -0
  24. package/dist/types/rules/RuleProcessor.d.ts.map +1 -0
  25. package/dist/types/rules/index.d.ts +2 -0
  26. package/dist/types/rules/index.d.ts.map +1 -0
  27. package/dist/types/storage/MemoryStorage.d.ts +50 -0
  28. package/dist/types/storage/MemoryStorage.d.ts.map +1 -0
  29. package/dist/types/storage/StorageAdapter.d.ts +39 -0
  30. package/dist/types/storage/StorageAdapter.d.ts.map +1 -0
  31. package/dist/types/storage/index.d.ts +3 -0
  32. package/dist/types/storage/index.d.ts.map +1 -0
  33. package/dist/types/types/index.d.ts +353 -0
  34. package/dist/types/types/index.d.ts.map +1 -0
  35. package/package.json +37 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../node_modules/uuid/dist/stringify.js","../node_modules/uuid/dist/rng.js","../node_modules/uuid/dist/native.js","../node_modules/uuid/dist/v4.js","../src/conversation/ConversationManager.ts","../src/rules/RuleProcessor.ts","../src/engine/TemplateEngine.ts","../src/engine/EventEmitter.ts","../src/engine/ConditionEvaluator.ts","../src/storage/StorageAdapter.ts","../src/storage/MemoryStorage.ts","../src/engine/ChatEngine.ts"],"sourcesContent":["import validate from './validate.js';\nconst byteToHex = [];\nfor (let i = 0; i < 256; ++i) {\n byteToHex.push((i + 0x100).toString(16).slice(1));\n}\nexport function unsafeStringify(arr, offset = 0) {\n return (byteToHex[arr[offset + 0]] +\n byteToHex[arr[offset + 1]] +\n byteToHex[arr[offset + 2]] +\n byteToHex[arr[offset + 3]] +\n '-' +\n byteToHex[arr[offset + 4]] +\n byteToHex[arr[offset + 5]] +\n '-' +\n byteToHex[arr[offset + 6]] +\n byteToHex[arr[offset + 7]] +\n '-' +\n byteToHex[arr[offset + 8]] +\n byteToHex[arr[offset + 9]] +\n '-' +\n byteToHex[arr[offset + 10]] +\n byteToHex[arr[offset + 11]] +\n byteToHex[arr[offset + 12]] +\n byteToHex[arr[offset + 13]] +\n byteToHex[arr[offset + 14]] +\n byteToHex[arr[offset + 15]]).toLowerCase();\n}\nfunction stringify(arr, offset = 0) {\n const uuid = unsafeStringify(arr, offset);\n if (!validate(uuid)) {\n throw TypeError('Stringified UUID is invalid');\n }\n return uuid;\n}\nexport default stringify;\n","let getRandomValues;\nconst rnds8 = new Uint8Array(16);\nexport default function rng() {\n if (!getRandomValues) {\n if (typeof crypto === 'undefined' || !crypto.getRandomValues) {\n throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');\n }\n getRandomValues = crypto.getRandomValues.bind(crypto);\n }\n return getRandomValues(rnds8);\n}\n","const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto);\nexport default { randomUUID };\n","import native from './native.js';\nimport rng from './rng.js';\nimport { unsafeStringify } from './stringify.js';\nfunction _v4(options, buf, offset) {\n options = options || {};\n const rnds = options.random ?? options.rng?.() ?? rng();\n if (rnds.length < 16) {\n throw new Error('Random bytes length must be >= 16');\n }\n rnds[6] = (rnds[6] & 0x0f) | 0x40;\n rnds[8] = (rnds[8] & 0x3f) | 0x80;\n if (buf) {\n offset = offset || 0;\n if (offset < 0 || offset + 16 > buf.length) {\n throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);\n }\n for (let i = 0; i < 16; ++i) {\n buf[offset + i] = rnds[i];\n }\n return buf;\n }\n return unsafeStringify(rnds);\n}\nfunction v4(options, buf, offset) {\n if (native.randomUUID && !buf && !options) {\n return native.randomUUID();\n }\n return _v4(options, buf, offset);\n}\nexport default v4;\n","import { v4 as uuidv4 } from 'uuid';\nimport { StorageAdapter } from '../storage/StorageAdapter';\nimport type { ConversationMessage, ConversationNode, ConversationState, UserAnswer, ValidatorFunction } from '../types';\n\n/**\n * ConversationManager handles the lifecycle and state of conversations\n */\nexport class ConversationManager {\n private storage: StorageAdapter;\n private customValidators: Record<string, ValidatorFunction>;\n\n constructor(\n storage: StorageAdapter,\n customValidators: Record<string, ValidatorFunction> = {}\n ) {\n this.storage = storage;\n this.customValidators = customValidators;\n }\n\n /**\n * Create a new conversation\n */\n async createConversation(\n conversationId: string,\n flowId: string,\n startNodeId: string,\n initialContext: Record<string, unknown> = {}\n ): Promise<ConversationState> {\n // Check if conversation already exists\n const existing = await this.storage.get(conversationId);\n if (existing) {\n throw new Error(`Conversation with ID '${conversationId}' already exists`);\n }\n\n const now = new Date();\n\n const state: ConversationState = {\n conversationId,\n flowId,\n currentNodeId: startNodeId,\n status: 'active',\n answers: [],\n messages: [],\n context: initialContext,\n startedAt: now,\n updatedAt: now,\n };\n\n await this.storage.save(state);\n return state;\n }\n\n /**\n * Get an existing conversation\n */\n async getConversation(conversationId: string): Promise<ConversationState | null> {\n return this.storage.get(conversationId);\n }\n\n /**\n * Check if a conversation exists\n */\n async conversationExists(conversationId: string): Promise<boolean> {\n return this.storage.exists(conversationId);\n }\n\n /**\n * Add a bot message to the conversation\n */\n async addBotMessage(\n conversationId: string,\n node: ConversationNode\n ): Promise<ConversationState> {\n const state = await this.getConversationOrThrow(conversationId);\n\n const message: ConversationMessage = {\n id: uuidv4(),\n role: 'bot',\n content: node.message,\n nodeId: node.id,\n timestamp: new Date(),\n metadata: node.metadata,\n };\n\n state.messages.push(message);\n state.updatedAt = new Date();\n\n await this.storage.save(state);\n return state;\n }\n\n /**\n * Record a user's answer and add it to the conversation\n */\n async recordUserAnswer(\n conversationId: string,\n nodeId: string,\n value?: string,\n selectedOptions?: string[],\n metadata?: Record<string, unknown>\n ): Promise<ConversationState> {\n const state = await this.getConversationOrThrow(conversationId);\n\n // Create the answer record\n const answer: UserAnswer = {\n nodeId,\n value,\n selectedOptions,\n timestamp: new Date(),\n metadata,\n };\n\n state.answers.push(answer);\n\n // Also add as a message for history\n const messageContent = this.formatAnswerAsMessage(value, selectedOptions);\n const message: ConversationMessage = {\n id: uuidv4(),\n role: 'user',\n content: messageContent,\n nodeId,\n timestamp: new Date(),\n metadata,\n };\n\n state.messages.push(message);\n state.updatedAt = new Date();\n\n await this.storage.save(state);\n return state;\n }\n\n /**\n * Validate user input against node validation rules\n */\n validateInput(\n node: ConversationNode,\n value: string | undefined,\n selectedOptions: string[] | undefined,\n state: ConversationState\n ): { valid: boolean; error?: string } {\n const validation = node.validation;\n\n // No validation rules - always valid\n if (!validation) {\n return { valid: true };\n }\n\n // Check required\n if (validation.required) {\n const hasValue = value !== undefined && value.trim() !== '';\n const hasSelection = selectedOptions !== undefined && selectedOptions.length > 0;\n\n if (!hasValue && !hasSelection) {\n return { valid: false, error: 'This field is required' };\n }\n }\n\n // For text-based validation, we need a value\n if (value !== undefined) {\n // Check minLength\n if (validation.minLength !== undefined && value.length < validation.minLength) {\n return {\n valid: false,\n error: `Minimum length is ${validation.minLength} characters`,\n };\n }\n\n // Check maxLength\n if (validation.maxLength !== undefined && value.length > validation.maxLength) {\n return {\n valid: false,\n error: `Maximum length is ${validation.maxLength} characters`,\n };\n }\n\n // Check pattern (regex)\n if (validation.pattern) {\n try {\n const regex = new RegExp(validation.pattern);\n if (!regex.test(value)) {\n return { valid: false, error: 'Invalid format' };\n }\n } catch {\n console.warn(`Invalid validation pattern: ${validation.pattern}`);\n }\n }\n }\n\n // Check custom validator\n if (validation.customValidator) {\n const validator = this.customValidators[validation.customValidator];\n if (validator) {\n const answer: UserAnswer = {\n nodeId: node.id,\n value,\n selectedOptions,\n timestamp: new Date(),\n };\n\n const result = validator(value || '', answer, state);\n\n if (typeof result === 'string') {\n return { valid: false, error: result };\n }\n if (!result) {\n return { valid: false, error: 'Validation failed' };\n }\n } else {\n console.warn(`Custom validator not found: ${validation.customValidator}`);\n }\n }\n\n // Validate selected options exist in node options\n if (selectedOptions && node.options) {\n const validOptionIds = new Set(node.options.map((o) => o.value));\n const invalidOptions = selectedOptions.filter((id) => !validOptionIds.has(id));\n\n if (invalidOptions.length > 0) {\n return {\n valid: false,\n error: `Invalid options selected: ${invalidOptions.join(', ')}`,\n };\n }\n }\n\n return { valid: true };\n }\n\n /**\n * Update the current node in the conversation\n */\n async updateCurrentNode(\n conversationId: string,\n nodeId: string\n ): Promise<ConversationState> {\n const state = await this.getConversationOrThrow(conversationId);\n\n state.currentNodeId = nodeId;\n state.updatedAt = new Date();\n\n await this.storage.save(state);\n return state;\n }\n\n /**\n * Update the conversation status\n */\n async updateStatus(\n conversationId: string,\n status: ConversationState['status']\n ): Promise<ConversationState> {\n const state = await this.getConversationOrThrow(conversationId);\n\n state.status = status;\n state.updatedAt = new Date();\n\n await this.storage.save(state);\n return state;\n }\n\n /**\n * Update or merge context data\n */\n async updateContext(\n conversationId: string,\n context: Record<string, unknown>,\n merge: boolean = true\n ): Promise<ConversationState> {\n const state = await this.getConversationOrThrow(conversationId);\n\n if (merge) {\n state.context = { ...state.context, ...context };\n } else {\n state.context = context;\n }\n\n state.updatedAt = new Date();\n\n await this.storage.save(state);\n return state;\n }\n\n /**\n * Get a specific answer by node ID\n */\n async getAnswer(\n conversationId: string,\n nodeId: string\n ): Promise<UserAnswer | null> {\n const state = await this.storage.get(conversationId);\n if (!state) {\n return null;\n }\n\n // Return the most recent answer for this node\n const answers = state.answers.filter((a) => a.nodeId === nodeId);\n return answers.length > 0 ? answers[answers.length - 1] : null;\n }\n\n /**\n * Get all answers for a conversation\n */\n async getAllAnswers(conversationId: string): Promise<UserAnswer[]> {\n const state = await this.storage.get(conversationId);\n return state?.answers || [];\n }\n\n /**\n * Get the message history\n */\n async getMessageHistory(conversationId: string): Promise<ConversationMessage[]> {\n const state = await this.storage.get(conversationId);\n return state?.messages || [];\n }\n\n /**\n * Delete a conversation\n */\n async deleteConversation(conversationId: string): Promise<void> {\n await this.storage.delete(conversationId);\n }\n\n /**\n * Reset a conversation to its initial state\n */\n async resetConversation(\n conversationId: string,\n startNodeId: string\n ): Promise<ConversationState> {\n const state = await this.getConversationOrThrow(conversationId);\n\n state.currentNodeId = startNodeId;\n state.status = 'active';\n state.answers = [];\n state.messages = [];\n state.updatedAt = new Date();\n\n await this.storage.save(state);\n return state;\n }\n\n /**\n * Register a custom validator at runtime\n */\n registerValidator(name: string, validator: ValidatorFunction): void {\n this.customValidators[name] = validator;\n }\n\n /**\n * Get conversation or throw an error if not found\n */\n private async getConversationOrThrow(\n conversationId: string\n ): Promise<ConversationState> {\n const state = await this.storage.get(conversationId);\n if (!state) {\n throw new Error(`Conversation not found: ${conversationId}`);\n }\n return state;\n }\n\n /**\n * Format an answer as a display message\n */\n private formatAnswerAsMessage(\n value?: string,\n selectedOptions?: string[]\n ): string {\n if (value !== undefined && value !== '') {\n return value;\n }\n if (selectedOptions && selectedOptions.length > 0) {\n return selectedOptions.join(', ');\n }\n return '';\n }\n}","import type { ConversationFlow, ConversationNode, ConversationState, EvaluatorFunction, RuleCondition, TransitionRule, UserAnswer } from \"../types\";\n\n/**\n * RuleProcessor handles the evaluation of transition rules\n * and determines the next node in the conversation flow\n * \n * Supports:\n * - AND/OR condition logic\n * - Nested conditions\n * - Context and state-based conditions\n * - Custom evaluators\n */\nexport class RuleProcessor {\n private flow: ConversationFlow;\n private customEvaluators: Record<string, EvaluatorFunction>;\n private enableLogging: boolean;\n\n constructor(\n flow: ConversationFlow,\n customEvaluators: Record<string, EvaluatorFunction> = {},\n enableLogging: boolean = false\n ) {\n this.flow = flow;\n this.customEvaluators = customEvaluators;\n this.enableLogging = enableLogging;\n }\n\n /**\n * Get a node by its ID\n */\n getNode(nodeId: string): ConversationNode | null {\n return this.flow.nodes.find((node) => node.id === nodeId) || null;\n }\n\n /**\n * Get the starting node of the flow\n */\n getStartNode(): ConversationNode | null {\n return this.getNode(this.flow.startNodeId);\n }\n\n /**\n * Check if a node exists in the flow\n */\n nodeExists(nodeId: string): boolean {\n return this.flow.nodes.some((node) => node.id === nodeId);\n }\n\n /**\n * Get all node IDs in the flow\n */\n getAllNodeIds(): string[] {\n return this.flow.nodes.map((node) => node.id);\n }\n\n /**\n * Determine the next node based on the current node and user's answer\n */\n getNextNode(\n currentNodeId: string,\n answer: UserAnswer,\n state: ConversationState\n ): ConversationNode | null {\n // Get all transitions from the current node\n const transitions = this.flow.transitions\n .filter((t) => t.fromNodeId === currentNodeId)\n .sort((a, b) => (b.priority || 0) - (a.priority || 0));\n\n this.log(`Evaluating ${transitions.length} transitions from '${currentNodeId}'`);\n\n // Find the first matching transition\n for (const transition of transitions) {\n const matches = this.evaluateTransition(transition, answer, state);\n \n if (matches) {\n this.log(`Transition matched: ${currentNodeId} -> ${transition.toNodeId}`);\n return this.getNode(transition.toNodeId);\n }\n }\n\n this.log(`No matching transition found from '${currentNodeId}'`);\n return null;\n }\n\n /**\n * Evaluate if a transition's conditions are met\n */\n private evaluateTransition(\n transition: TransitionRule,\n answer: UserAnswer,\n state: ConversationState\n ): boolean {\n // If no conditions, transition always applies (fallback/default)\n if (!transition.conditions || transition.conditions.length === 0) {\n return true;\n }\n\n const logic = transition.conditionLogic || 'and';\n\n if (logic === 'or') {\n // OR logic: at least one condition must be true\n return transition.conditions.some((condition) =>\n this.evaluateCondition(condition, answer, state)\n );\n } else {\n // AND logic (default): all conditions must be true\n return transition.conditions.every((condition) =>\n this.evaluateCondition(condition, answer, state)\n );\n }\n }\n\n /**\n * Evaluate a single condition (supports nested conditions)\n */\n evaluateCondition(\n condition: RuleCondition,\n answer: UserAnswer,\n state: ConversationState\n ): boolean {\n // Check if this is a nested condition group\n if (condition.logic && condition.conditions && condition.conditions.length > 0) {\n return this.evaluateNestedConditions(condition, answer, state);\n }\n\n // Simple condition evaluation\n return this.evaluateSimpleCondition(condition, answer, state);\n }\n\n /**\n * Evaluate nested conditions with AND/OR logic\n */\n private evaluateNestedConditions(\n condition: RuleCondition,\n answer: UserAnswer,\n state: ConversationState\n ): boolean {\n const { logic, conditions } = condition;\n\n if (!conditions || conditions.length === 0) {\n return true;\n }\n\n this.log(`Evaluating nested ${logic?.toUpperCase()} condition group with ${conditions.length} conditions`);\n\n if (logic === 'or') {\n // OR logic: at least one must be true\n for (const subCondition of conditions) {\n if (this.evaluateCondition(subCondition, answer, state)) {\n this.log('OR condition group: found true condition');\n return true;\n }\n }\n return false;\n } else {\n // AND logic (default): all must be true\n for (const subCondition of conditions) {\n if (!this.evaluateCondition(subCondition, answer, state)) {\n this.log('AND condition group: found false condition');\n return false;\n }\n }\n return true;\n }\n }\n\n /**\n * Evaluate a simple (non-nested) condition\n */\n private evaluateSimpleCondition(\n condition: RuleCondition,\n answer: UserAnswer,\n state: ConversationState\n ): boolean {\n const { field, operator } = condition;\n\n // Handle 'always' operator - always returns true\n if (operator === 'always') {\n return true;\n }\n\n // Handle custom evaluator\n if (field === 'custom' && condition.customEvaluator) {\n const evaluator = this.customEvaluators[condition.customEvaluator];\n if (evaluator) {\n const result = evaluator(answer, state, condition);\n this.log(`Custom evaluator '${condition.customEvaluator}' returned: ${result}`);\n return result;\n }\n this.log(`Custom evaluator not found: ${condition.customEvaluator}`);\n return false;\n }\n\n // Get the actual value based on field type\n const actualValue = this.getFieldValue(condition, answer, state);\n const expectedValue = condition.value;\n\n // Handle exists/not_exists operators\n if (operator === 'exists') {\n return actualValue !== null && actualValue !== undefined && actualValue !== '';\n }\n\n if (operator === 'not_exists') {\n return actualValue === null || actualValue === undefined || actualValue === '';\n }\n\n // Evaluate based on operator\n const result = this.compareValues(operator, actualValue, expectedValue);\n this.log(`Condition: ${field}[${condition.sourceId || ''}] ${operator} ${expectedValue} => ${result} (actual: ${actualValue})`);\n\n return result;\n }\n\n /**\n * Extract the value based on field type\n */\n private getFieldValue(\n condition: RuleCondition,\n answer: UserAnswer,\n state: ConversationState\n ): string | string[] | number | boolean | undefined {\n const { field, sourceId } = condition;\n\n switch (field) {\n case 'value':\n return answer.value;\n\n case 'selectedOptions':\n return answer.selectedOptions;\n\n case 'context':\n if (!sourceId) return undefined;\n return this.getNestedValue(state.context, sourceId);\n\n case 'state':\n if (!sourceId) return undefined;\n return this.getStateValue(state, sourceId);\n\n default:\n // Default to answer value for backward compatibility\n return answer.value;\n }\n }\n\n /**\n * Get a value from the conversation context (supports dot notation)\n */\n private getNestedValue(\n obj: Record<string, unknown>,\n path: string\n ): string | number | boolean | undefined {\n const parts = path.split('.');\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === null || current === undefined) {\n return undefined;\n }\n if (typeof current !== 'object') {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n\n if (current === null || current === undefined) {\n return undefined;\n }\n\n if (typeof current === 'object') {\n return JSON.stringify(current);\n }\n\n return current as string | number | boolean;\n }\n\n /**\n * Get a value from the conversation state\n */\n private getStateValue(\n state: ConversationState,\n property: string\n ): string | number | boolean | undefined {\n switch (property) {\n case 'status':\n return state.status;\n case 'answersCount':\n return state.answers.length;\n case 'messagesCount':\n return state.messages.length;\n case 'flowId':\n return state.flowId;\n case 'currentNodeId':\n return state.currentNodeId;\n case 'conversationId':\n return state.conversationId;\n default:\n return undefined;\n }\n }\n\n /**\n * Compare values using the specified operator\n */\n private compareValues(\n operator: RuleCondition['operator'],\n actual: string | string[] | number | boolean | undefined,\n expected: string | number | boolean | string[] | undefined\n ): boolean {\n switch (operator) {\n case 'equals':\n return this.normalizeValue(actual) === this.normalizeValue(expected);\n\n case 'not_equals':\n return this.normalizeValue(actual) !== this.normalizeValue(expected);\n\n case 'contains':\n return this.checkContains(actual, expected);\n\n case 'not_contains':\n return !this.checkContains(actual, expected);\n\n case 'matches':\n if (typeof actual !== 'string' || typeof expected !== 'string') {\n return false;\n }\n try {\n const regex = new RegExp(expected, 'i');\n return regex.test(actual);\n } catch {\n this.log(`Invalid regex pattern: ${expected}`);\n return false;\n }\n\n case 'greater_than':\n return this.compareNumeric(actual, expected, (a, b) => a > b);\n\n case 'less_than':\n return this.compareNumeric(actual, expected, (a, b) => a < b);\n\n case 'greater_than_or_equals':\n return this.compareNumeric(actual, expected, (a, b) => a >= b);\n\n case 'less_than_or_equals':\n return this.compareNumeric(actual, expected, (a, b) => a <= b);\n\n case 'in':\n if (!Array.isArray(expected)) {\n return false;\n }\n return expected\n .map((v) => this.normalizeValue(v))\n .includes(this.normalizeValue(actual));\n\n case 'not_in':\n if (!Array.isArray(expected)) {\n return true;\n }\n return !expected\n .map((v) => this.normalizeValue(v))\n .includes(this.normalizeValue(actual));\n\n case 'exists':\n return actual !== null && actual !== undefined && actual !== '';\n\n case 'not_exists':\n return actual === null || actual === undefined || actual === '';\n\n case 'always':\n return true;\n\n default:\n this.log(`Unknown operator: ${operator}`);\n return false;\n }\n }\n\n /**\n * Normalize a value for comparison (lowercase strings)\n */\n private normalizeValue(\n value: string | string[] | number | boolean | undefined\n ): string {\n if (value === undefined || value === null) {\n return '';\n }\n if (Array.isArray(value)) {\n return value\n .map((v) => String(v).toLowerCase().trim())\n .sort()\n .join(',');\n }\n return String(value).toLowerCase().trim();\n }\n\n /**\n * Check if actual contains expected\n */\n private checkContains(\n actual: string | string[] | number | boolean | undefined,\n expected: string | number | boolean | string[] | undefined\n ): boolean {\n if (actual === undefined || actual === null) {\n return false;\n }\n\n const expectedStr = String(expected).toLowerCase();\n\n if (Array.isArray(actual)) {\n return actual.some((v) => String(v).toLowerCase().includes(expectedStr));\n }\n\n return String(actual).toLowerCase().includes(expectedStr);\n }\n\n /**\n * Compare numeric values\n */\n private compareNumeric(\n actual: string | string[] | number | boolean | undefined,\n expected: string | number | boolean | string[] | undefined,\n compareFn: (a: number, b: number) => boolean\n ): boolean {\n const actualNum = Number(actual);\n const expectedNum = Number(expected);\n\n if (isNaN(actualNum) || isNaN(expectedNum)) {\n return false;\n }\n\n return compareFn(actualNum, expectedNum);\n }\n\n /**\n * Validate that all node references in transitions exist\n */\n validateFlow(): { valid: boolean; errors: string[] } {\n const errors: string[] = [];\n const nodeIds = new Set(this.flow.nodes.map((n) => n.id));\n\n // Check start node exists\n if (!nodeIds.has(this.flow.startNodeId)) {\n errors.push(`Start node '${this.flow.startNodeId}' does not exist`);\n }\n\n // Check all transition references\n for (const transition of this.flow.transitions) {\n if (!nodeIds.has(transition.fromNodeId)) {\n errors.push(\n `Transition references non-existent fromNodeId: '${transition.fromNodeId}'`\n );\n }\n if (!nodeIds.has(transition.toNodeId)) {\n errors.push(\n `Transition references non-existent toNodeId: '${transition.toNodeId}'`\n );\n }\n\n // Validate nested conditions\n if (transition.conditions) {\n this.validateConditions(transition.conditions, errors);\n }\n }\n\n // Check for nodes with no outgoing transitions (except 'end' type)\n for (const node of this.flow.nodes) {\n if (node.type === 'end') {\n continue;\n }\n const hasOutgoing = this.flow.transitions.some(\n (t) => t.fromNodeId === node.id\n );\n if (!hasOutgoing) {\n errors.push(`Node '${node.id}' has no outgoing transitions`);\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n }\n\n /**\n * Validate conditions recursively\n */\n private validateConditions(conditions: RuleCondition[], errors: string[]): void {\n for (const condition of conditions) {\n // Validate nested conditions\n if (condition.conditions && condition.conditions.length > 0) {\n if (!condition.logic) {\n errors.push('Nested condition group missing logic (and/or)');\n }\n this.validateConditions(condition.conditions, errors);\n } else {\n // Validate simple condition\n if (!condition.operator && condition.field !== 'custom') {\n errors.push('Condition missing operator');\n }\n if (condition.field === 'custom' && !condition.customEvaluator) {\n errors.push('Custom condition missing evaluator name');\n }\n }\n }\n }\n\n /**\n * Get all possible next nodes from a given node (for debugging/visualization)\n */\n getPossibleNextNodes(nodeId: string): ConversationNode[] {\n const transitions = this.flow.transitions.filter(\n (t) => t.fromNodeId === nodeId\n );\n const nextNodes: ConversationNode[] = [];\n\n for (const transition of transitions) {\n const node = this.getNode(transition.toNodeId);\n if (node && !nextNodes.find((n) => n.id === node.id)) {\n nextNodes.push(node);\n }\n }\n\n return nextNodes;\n }\n\n /**\n * Evaluate a set of conditions without an answer (for node actions)\n * Uses state and context only\n */\n evaluateConditionsWithoutAnswer(\n conditions: RuleCondition[],\n state: ConversationState,\n logic: 'and' | 'or' = 'and'\n ): boolean {\n // Create a dummy answer for evaluation\n const dummyAnswer: UserAnswer = {\n nodeId: state.currentNodeId,\n timestamp: new Date(),\n };\n\n if (logic === 'or') {\n return conditions.some((condition) =>\n this.evaluateCondition(condition, dummyAnswer, state)\n );\n } else {\n return conditions.every((condition) =>\n this.evaluateCondition(condition, dummyAnswer, state)\n );\n }\n }\n\n /**\n * Register a custom evaluator at runtime\n */\n registerEvaluator(name: string, evaluator: EvaluatorFunction): void {\n this.customEvaluators[name] = evaluator;\n }\n\n /**\n * Update the conversation flow\n */\n setFlow(flow: ConversationFlow): void {\n this.flow = flow;\n }\n\n /**\n * Get the current flow\n */\n getFlow(): ConversationFlow {\n return this.flow;\n }\n\n /**\n * Internal logging helper\n */\n private log(message: string): void {\n if (this.enableLogging) {\n console.log(`[RuleProcessor] ${message}`);\n }\n }\n}","import type { ConversationNode, ConversationState, UserAnswer, VariableResolver } from \"../types\";\n\n/**\n * TemplateEngine handles variable substitution in messages\n * Supports {{variableName}} syntax with built-in and custom resolvers\n */\nexport class TemplateEngine {\n private customResolvers: Record<string, VariableResolver>;\n\n constructor(customResolvers: Record<string, VariableResolver> = {}) {\n this.customResolvers = customResolvers;\n }\n\n /**\n * Process a message template and replace all variables\n */\n async processTemplate(\n template: string,\n state: ConversationState,\n node: ConversationNode\n ): Promise<string> {\n // Match all {{variableName}} patterns\n const variablePattern = /\\{\\{([^}]+)\\}\\}/g;\n const matches = template.matchAll(variablePattern);\n\n let result = template;\n\n for (const match of matches) {\n const fullMatch = match[0]; // e.g., \"{{userName}}\"\n const variablePath = match[1].trim(); // e.g., \"userName\" or \"answers.name.value\"\n\n const value = await this.resolveVariable(variablePath, state, node);\n const stringValue = this.formatValue(value);\n\n result = result.replace(fullMatch, stringValue);\n }\n\n return result;\n }\n\n /**\n * Resolve a variable to its value\n */\n private async resolveVariable(\n variablePath: string,\n state: ConversationState,\n node: ConversationNode\n ): Promise<string | number | boolean | null> {\n // Check for custom resolver first (format: resolver:argument)\n if (variablePath.includes(':')) {\n const [resolverName, ...args] = variablePath.split(':');\n const resolver = this.customResolvers[resolverName];\n\n if (resolver) {\n return resolver(args.join(':'), state, node);\n }\n }\n\n // Check if it's a custom resolver without arguments\n if (this.customResolvers[variablePath]) {\n return this.customResolvers[variablePath](variablePath, state, node);\n }\n\n // Built-in resolvers\n return this.resolveBuiltIn(variablePath, state, node);\n }\n\n /**\n * Built-in variable resolvers\n */\n private resolveBuiltIn(\n variablePath: string,\n state: ConversationState,\n node: ConversationNode\n ): string | number | boolean | null {\n // Handle dot notation for nested access\n const parts = variablePath.split('.');\n\n // Context variables: {{context.userName}}\n if (parts[0] === 'context') {\n return this.getNestedValue(state.context, parts.slice(1));\n }\n\n // Answer variables: {{answer.nodeId}} or {{answer.nodeId.value}}\n if (parts[0] === 'answer' && parts.length >= 2) {\n const nodeId = parts[1];\n // Get the MOST RECENT answer for this node\n const answer = this.getMostRecentAnswer(state, nodeId);\n\n if (!answer) return null;\n\n if (parts.length === 2) {\n // Return the value or selected options\n return answer.value || answer.selectedOptions?.join(', ') || null;\n }\n\n if (parts[2] === 'value') return answer.value || null;\n if (parts[2] === 'selectedOptions') return answer.selectedOptions?.join(', ') || null;\n if (parts[2] === 'timestamp') return answer.timestamp.toString();\n\n return null;\n }\n\n // Shorthand: {{nodeId}} - get answer value directly\n const simpleAnswer = this.getMostRecentAnswer(state, variablePath);\n if (simpleAnswer) {\n return simpleAnswer.value || simpleAnswer.selectedOptions?.join(', ') || null;\n }\n\n // State variables\n if (variablePath === 'conversationId') return state.conversationId;\n if (variablePath === 'flowId') return state.flowId;\n if (variablePath === 'currentNodeId') return state.currentNodeId;\n if (variablePath === 'status') return state.status;\n if (variablePath === 'answersCount') return state.answers.length;\n if (variablePath === 'messagesCount') return state.messages.length;\n\n // Node variables: {{node.id}}, {{node.type}}\n if (parts[0] === 'node') {\n if (parts[1] === 'id') return node.id;\n if (parts[1] === 'type') return node.type || 'text';\n if (parts[1] === 'message') return node.message;\n return null;\n }\n\n // Date/time variables\n if (variablePath === 'now') return new Date().toLocaleString();\n if (variablePath === 'today') return new Date().toLocaleDateString();\n if (variablePath === 'time') return new Date().toLocaleTimeString();\n\n return null;\n }\n\n /**\n * Get the most recent answer for a given node ID\n */\n private getMostRecentAnswer(\n state: ConversationState,\n nodeId: string\n ): UserAnswer | null {\n // Iterate from the end to find the most recent answer\n for (let i = state.answers.length - 1; i >= 0; i--) {\n if (state.answers[i].nodeId === nodeId) {\n return state.answers[i];\n }\n }\n return null;\n }\n\n /**\n * Get a nested value from an object using dot notation\n */\n private getNestedValue(\n obj: Record<string, unknown>,\n parts: string[]\n ): string | number | boolean | null {\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === null || current === undefined) {\n return null;\n }\n if (typeof current !== 'object') {\n return null;\n }\n current = (current as Record<string, unknown>)[part];\n }\n\n if (current === null || current === undefined) {\n return null;\n }\n\n if (typeof current === 'object') {\n return JSON.stringify(current);\n }\n\n return current as string | number | boolean;\n }\n\n /**\n * Format a value for display in a message\n */\n private formatValue(value: string | number | boolean | null): string {\n if (value === null || value === undefined) {\n return '[unknown]';\n }\n return String(value);\n }\n\n /**\n * Register a custom resolver at runtime\n */\n registerResolver(name: string, resolver: VariableResolver): void {\n this.customResolvers[name] = resolver;\n }\n\n /**\n * Check if a message contains template variables\n */\n hasVariables(message: string): boolean {\n return /\\{\\{[^}]+\\}\\}/.test(message);\n }\n}","import type { ChatEventPayloadMap, ChatEventType, EventHandler, EventHandlers } from \"../types\";\n\n/**\n * EventEmitter handles the hooks/events system for the ChatEngine\n * Allows subscribing to and emitting events throughout the conversation lifecycle\n */\nexport class EventEmitter {\n private handlers: Map<ChatEventType, EventHandler<ChatEventType>[]>;\n private enableLogging: boolean;\n\n constructor(initialHandlers?: EventHandlers, enableLogging: boolean = false) {\n this.handlers = new Map();\n this.enableLogging = enableLogging;\n\n // Register initial handlers if provided\n if (initialHandlers) {\n this.registerHandlers(initialHandlers);\n }\n }\n\n /**\n * Register multiple event handlers from a configuration object\n */\n registerHandlers(handlers: EventHandlers): void {\n const eventTypes = Object.keys(handlers) as ChatEventType[];\n\n for (const eventType of eventTypes) {\n const handler = handlers[eventType];\n\n if (!handler) continue;\n\n // Ensure the handlers array exists\n if (!this.handlers.has(eventType)) {\n this.handlers.set(eventType, []);\n }\n\n const handlersArray = this.handlers.get(eventType)!;\n\n if (Array.isArray(handler)) {\n // Multiple handlers for this event type\n for (const h of handler) {\n handlersArray.push(h as EventHandler<ChatEventType>);\n }\n } else {\n // Single handler\n handlersArray.push(handler as EventHandler<ChatEventType>);\n }\n\n this.log(`Handler(s) registered for event: ${eventType}`);\n }\n }\n\n /**\n * Subscribe to an event\n * @param eventType - The type of event to listen for\n * @param handler - The function to call when the event is emitted\n * @returns A function to unsubscribe the handler\n */\n on<T extends ChatEventType>(\n eventType: T,\n handler: EventHandler<T>\n ): () => void {\n if (!this.handlers.has(eventType)) {\n this.handlers.set(eventType, []);\n }\n\n const handlers = this.handlers.get(eventType)!;\n handlers.push(handler as EventHandler<ChatEventType>);\n\n this.log(`Handler registered for event: ${eventType}`);\n\n // Return unsubscribe function\n return () => {\n this.off(eventType, handler);\n };\n }\n\n /**\n * Subscribe to an event for a single emission only\n * @param eventType - The type of event to listen for\n * @param handler - The function to call when the event is emitted\n * @returns A function to unsubscribe the handler\n */\n once<T extends ChatEventType>(\n eventType: T,\n handler: EventHandler<T>\n ): () => void {\n const wrappedHandler: EventHandler<T> = (payload) => {\n this.off(eventType, wrappedHandler);\n return handler(payload);\n };\n\n return this.on(eventType, wrappedHandler);\n }\n\n /**\n * Unsubscribe from an event\n * @param eventType - The type of event to unsubscribe from\n * @param handler - The specific handler to remove (if not provided, removes all handlers)\n */\n off<T extends ChatEventType>(\n eventType: T,\n handler?: EventHandler<T>\n ): void {\n if (!handler) {\n // Remove all handlers for this event type\n this.handlers.delete(eventType);\n this.log(`All handlers removed for event: ${eventType}`);\n return;\n }\n\n const handlers = this.handlers.get(eventType);\n if (!handlers) return;\n\n const index = handlers.indexOf(handler as EventHandler<ChatEventType>);\n if (index !== -1) {\n handlers.splice(index, 1);\n this.log(`Handler removed for event: ${eventType}`);\n }\n\n // Clean up empty arrays\n if (handlers.length === 0) {\n this.handlers.delete(eventType);\n }\n }\n\n /**\n * Emit an event to all registered handlers\n * @param eventType - The type of event to emit\n * @param payload - The data to pass to handlers\n * @returns For onBeforeTransition, returns false if any handler returns false (cancels transition)\n */\n async emit<T extends ChatEventType>(\n eventType: T,\n payload: ChatEventPayloadMap[T]\n ): Promise<boolean> {\n const handlers = this.handlers.get(eventType);\n\n if (!handlers || handlers.length === 0) {\n this.log(`No handlers for event: ${eventType}`);\n return true;\n }\n\n this.log(`Emitting event: ${eventType} to ${handlers.length} handler(s)`);\n\n // For onBeforeTransition, we need to check if any handler returns false\n const isCancellable = eventType === 'onBeforeTransition';\n\n for (const handler of handlers) {\n try {\n const result = await handler(payload);\n\n // If this is a cancellable event and handler returns false, stop propagation\n if (isCancellable && result === false) {\n this.log(`Event ${eventType} cancelled by handler`);\n return false;\n }\n } catch (error) {\n console.error(`Error in event handler for ${eventType}:`, error);\n\n // Emit an error event (but don't recurse if this IS the error event)\n if (eventType !== 'onError') {\n await this.emit('onError', {\n conversationId: (payload as ChatEventPayloadMap[ChatEventType] & { conversationId?: string }).conversationId || 'unknown',\n timestamp: new Date(),\n error: error instanceof Error ? error : new Error(String(error)),\n context: `Error in ${eventType} handler`,\n });\n }\n }\n }\n\n return true;\n }\n\n /**\n * Check if there are any handlers registered for an event type\n */\n hasHandlers(eventType: ChatEventType): boolean {\n const handlers = this.handlers.get(eventType);\n return handlers !== undefined && handlers.length > 0;\n }\n\n /**\n * Get the number of handlers registered for an event type\n */\n handlerCount(eventType: ChatEventType): number {\n return this.handlers.get(eventType)?.length || 0;\n }\n\n /**\n * Get all registered event types\n */\n getRegisteredEvents(): ChatEventType[] {\n return Array.from(this.handlers.keys());\n }\n\n /**\n * Remove all handlers for all events\n */\n removeAllHandlers(): void {\n this.handlers.clear();\n this.log('All event handlers removed');\n }\n\n /**\n * Internal logging helper\n */\n private log(message: string): void {\n if (this.enableLogging) {\n console.log(`[EventEmitter] ${message}`);\n }\n }\n}","import type { AnswerOption, ConditionEvaluatorFunction, ConditionGroup, ConversationNode, \n ConversationState, DisplayCondition, DisplayConditions, UserAnswer } from \"../types\";\n\n/**\n * ConditionEvaluator handles the evaluation of display conditions\n * for options, messages, and nodes\n */\nexport class ConditionEvaluator {\n private customEvaluators: Record<string, ConditionEvaluatorFunction>;\n private enableLogging: boolean;\n\n constructor(\n customEvaluators: Record<string, ConditionEvaluatorFunction> = {},\n enableLogging: boolean = false\n ) {\n this.customEvaluators = customEvaluators;\n this.enableLogging = enableLogging;\n }\n\n /**\n * Filter options based on their display conditions\n * Returns only options that should be displayed\n */\n async filterOptions(\n options: AnswerOption[],\n state: ConversationState\n ): Promise<AnswerOption[]> {\n const filteredOptions: AnswerOption[] = [];\n\n for (const option of options) {\n // If no conditions, always show\n if (!option.displayConditions) {\n filteredOptions.push(option);\n continue;\n }\n\n const shouldDisplay = await this.evaluateConditions(\n option.displayConditions,\n state\n );\n\n if (shouldDisplay) {\n filteredOptions.push(option);\n } else {\n this.log(`Option '${option.value}' hidden by display conditions`);\n }\n }\n\n return filteredOptions;\n }\n\n /**\n * Get the appropriate message for a node based on conditions\n * Returns the conditional message if conditions match, otherwise the default message\n */\n async getConditionalMessage(\n node: ConversationNode,\n state: ConversationState\n ): Promise<string> {\n // If no conditional messages, return default\n if (!node.conditionalMessages || node.conditionalMessages.length === 0) {\n return node.message;\n }\n\n // Sort by priority (higher first)\n const sortedMessages = [...node.conditionalMessages].sort(\n (a, b) => (b.priority || 0) - (a.priority || 0)\n );\n\n // Find the first matching conditional message\n for (const conditionalMessage of sortedMessages) {\n const matches = await this.evaluateConditions(\n conditionalMessage.conditions,\n state\n );\n\n if (matches) {\n this.log(`Using conditional message for node '${node.id}'`);\n return conditionalMessage.message;\n }\n }\n\n // No conditions matched, return default message\n return node.message;\n }\n\n /**\n * Check if a node should be displayed based on its conditions\n */\n async shouldDisplayNode(\n node: ConversationNode,\n state: ConversationState\n ): Promise<boolean> {\n // If no conditions, always show\n if (!node.displayConditions) {\n return true;\n }\n\n return this.evaluateConditions(node.displayConditions, state);\n }\n\n /**\n * Evaluate display conditions\n * Supports single condition, array of conditions (AND), or condition groups\n */\n async evaluateConditions(\n conditions: DisplayConditions,\n state: ConversationState\n ): Promise<boolean> {\n // Single condition\n if (this.isSingleCondition(conditions)) {\n return this.evaluateSingleCondition(conditions, state);\n }\n\n // Array of conditions (implicit AND)\n if (Array.isArray(conditions)) {\n for (const condition of conditions) {\n const result = await this.evaluateConditions(condition, state);\n if (!result) return false; // AND logic: all must be true\n }\n return true;\n }\n\n // Condition group with explicit logic\n if (this.isConditionGroup(conditions)) {\n return this.evaluateConditionGroup(conditions, state);\n }\n\n // Unknown format, default to true\n this.log('Unknown condition format, defaulting to true');\n return true;\n }\n\n /**\n * Evaluate a condition group with AND/OR logic\n */\n private async evaluateConditionGroup(\n group: ConditionGroup,\n state: ConversationState\n ): Promise<boolean> {\n const { logic, conditions } = group;\n\n if (logic === 'and') {\n for (const condition of conditions) {\n const result = await this.evaluateConditions(condition, state);\n if (!result) return false;\n }\n return true;\n }\n\n if (logic === 'or') {\n for (const condition of conditions) {\n const result = await this.evaluateConditions(condition, state);\n if (result) return true;\n }\n return false;\n }\n\n return true;\n }\n\n /**\n * Evaluate a single display condition\n */\n private async evaluateSingleCondition(\n condition: DisplayCondition,\n state: ConversationState\n ): Promise<boolean> {\n const { source, operator } = condition;\n\n // Handle custom evaluator\n if (source === 'custom' || operator === 'custom') {\n return this.evaluateCustomCondition(condition, state);\n }\n\n // Get the actual value to compare\n const actualValue = this.getSourceValue(condition, state);\n\n // Handle exists/not_exists operators\n if (operator === 'exists') {\n return actualValue !== null && actualValue !== undefined;\n }\n\n if (operator === 'not_exists') {\n return actualValue === null || actualValue === undefined;\n }\n\n // Compare values\n return this.compareValues(operator, actualValue, condition.value);\n }\n\n /**\n * Get the value from the specified source\n */\n private getSourceValue(\n condition: DisplayCondition,\n state: ConversationState\n ): unknown {\n const { source, sourceId, field } = condition;\n\n switch (source) {\n case 'answer':\n return this.getAnswerValue(state, sourceId, field);\n\n case 'context':\n return this.getContextValue(state, sourceId, field);\n\n case 'state':\n return this.getStateValue(state, sourceId);\n\n case 'metadata':\n return this.getMetadataValue(state, sourceId, field);\n\n default:\n return null;\n }\n }\n\n /**\n * Get value from a previous answer\n */\n private getAnswerValue(\n state: ConversationState,\n nodeId?: string,\n field?: string\n ): unknown {\n if (!nodeId) return null;\n\n // Get the most recent answer for this node\n const answer = this.getMostRecentAnswer(state, nodeId);\n if (!answer) return null;\n\n // If no specific field, return the primary value\n if (!field) {\n return answer.value ?? answer.selectedOptions ?? null;\n }\n\n // Get specific field\n switch (field) {\n case 'value':\n return answer.value;\n case 'selectedOptions':\n return answer.selectedOptions;\n case 'timestamp':\n return answer.timestamp;\n default:\n // Check metadata\n return answer.metadata?.[field] ?? null;\n }\n }\n\n /**\n * Get the most recent answer for a given node ID\n */\n private getMostRecentAnswer(\n state: ConversationState,\n nodeId: string\n ): UserAnswer | null {\n for (let i = state.answers.length - 1; i >= 0; i--) {\n if (state.answers[i].nodeId === nodeId) {\n return state.answers[i];\n }\n }\n return null;\n }\n\n /**\n * Get value from conversation context\n */\n private getContextValue(\n state: ConversationState,\n key?: string,\n field?: string\n ): unknown {\n if (!key) return null;\n\n const value = state.context[key];\n\n // If field is specified, try to access nested property\n if (field && typeof value === 'object' && value !== null) {\n return (value as Record<string, unknown>)[field] ?? null;\n }\n\n return value ?? null;\n }\n\n /**\n * Get value from conversation state\n */\n private getStateValue(\n state: ConversationState,\n property?: string\n ): unknown {\n if (!property) return null;\n\n switch (property) {\n case 'status':\n return state.status;\n case 'answersCount':\n return state.answers.length;\n case 'messagesCount':\n return state.messages.length;\n case 'flowId':\n return state.flowId;\n case 'currentNodeId':\n return state.currentNodeId;\n case 'conversationId':\n return state.conversationId;\n default:\n return null;\n }\n }\n\n /**\n * Get value from metadata\n */\n private getMetadataValue(\n state: ConversationState,\n sourceId?: string,\n field?: string\n ): unknown {\n if (!sourceId) return null;\n\n // Get metadata from the most recent answer for this node\n const answer = this.getMostRecentAnswer(state, sourceId);\n if (!answer?.metadata) return null;\n\n if (field) {\n return answer.metadata[field] ?? null;\n }\n\n return answer.metadata;\n }\n\n /**\n * Evaluate a custom condition using registered evaluator\n */\n private async evaluateCustomCondition(\n condition: DisplayCondition,\n state: ConversationState\n ): Promise<boolean> {\n const evaluatorName = condition.customEvaluator;\n\n if (!evaluatorName) {\n this.log('Custom condition missing evaluator name');\n return false;\n }\n\n const evaluator = this.customEvaluators[evaluatorName];\n\n if (!evaluator) {\n this.log(`Custom condition evaluator not found: ${evaluatorName}`);\n return false;\n }\n\n try {\n return await evaluator(state, condition);\n } catch (error) {\n this.log(`Error in custom evaluator '${evaluatorName}': ${error}`);\n return false;\n }\n }\n\n /**\n * Compare two values using the specified operator\n */\n private compareValues(\n operator: DisplayCondition['operator'],\n actual: unknown,\n expected: unknown\n ): boolean {\n switch (operator) {\n case 'equals':\n return this.normalizeValue(actual) === this.normalizeValue(expected);\n\n case 'not_equals':\n return this.normalizeValue(actual) !== this.normalizeValue(expected);\n\n case 'contains':\n return this.checkContains(actual, expected);\n\n case 'not_contains':\n return !this.checkContains(actual, expected);\n\n case 'in':\n if (!Array.isArray(expected)) return false;\n return expected\n .map((v) => this.normalizeValue(v))\n .includes(this.normalizeValue(actual));\n\n case 'not_in':\n if (!Array.isArray(expected)) return true;\n return !expected\n .map((v) => this.normalizeValue(v))\n .includes(this.normalizeValue(actual));\n\n case 'greater_than':\n return this.compareNumeric(actual, expected, (a, b) => a > b);\n\n case 'less_than':\n return this.compareNumeric(actual, expected, (a, b) => a < b);\n\n case 'greater_than_or_equals':\n return this.compareNumeric(actual, expected, (a, b) => a >= b);\n\n case 'less_than_or_equals':\n return this.compareNumeric(actual, expected, (a, b) => a <= b);\n\n case 'matches':\n if (typeof actual !== 'string' || typeof expected !== 'string') {\n return false;\n }\n try {\n const regex = new RegExp(expected, 'i');\n return regex.test(actual);\n } catch {\n this.log(`Invalid regex pattern: ${expected}`);\n return false;\n }\n\n case 'exists':\n return actual !== null && actual !== undefined;\n\n case 'not_exists':\n return actual === null || actual === undefined;\n\n default:\n return false;\n }\n }\n\n /**\n * Normalize a value for comparison\n */\n private normalizeValue(value: unknown): string {\n if (value === null || value === undefined) {\n return '';\n }\n if (Array.isArray(value)) {\n return value\n .map((v) => String(v).toLowerCase().trim())\n .sort()\n .join(',');\n }\n return String(value).toLowerCase().trim();\n }\n\n /**\n * Check if actual contains expected\n */\n private checkContains(actual: unknown, expected: unknown): boolean {\n if (actual === null || actual === undefined) {\n return false;\n }\n\n const expectedStr = String(expected).toLowerCase();\n\n if (Array.isArray(actual)) {\n return actual.some((v) =>\n String(v).toLowerCase().includes(expectedStr)\n );\n }\n\n return String(actual).toLowerCase().includes(expectedStr);\n }\n\n /**\n * Compare numeric values\n */\n private compareNumeric(\n actual: unknown,\n expected: unknown,\n compareFn: (a: number, b: number) => boolean\n ): boolean {\n const actualNum = Number(actual);\n const expectedNum = Number(expected);\n\n if (isNaN(actualNum) || isNaN(expectedNum)) {\n return false;\n }\n\n return compareFn(actualNum, expectedNum);\n }\n\n /**\n * Type guard for single condition\n */\n private isSingleCondition(\n conditions: DisplayConditions\n ): conditions is DisplayCondition {\n return (\n typeof conditions === 'object' &&\n !Array.isArray(conditions) &&\n 'source' in conditions &&\n 'operator' in conditions\n );\n }\n\n /**\n * Type guard for condition group\n */\n private isConditionGroup(\n conditions: DisplayConditions\n ): conditions is ConditionGroup {\n return (\n typeof conditions === 'object' &&\n !Array.isArray(conditions) &&\n 'logic' in conditions &&\n 'conditions' in conditions\n );\n }\n\n /**\n * Register a custom condition evaluator\n */\n registerEvaluator(name: string, evaluator: ConditionEvaluatorFunction): void {\n this.customEvaluators[name] = evaluator;\n this.log(`Custom condition evaluator registered: ${name}`);\n }\n\n /**\n * Unregister a custom condition evaluator\n */\n unregisterEvaluator(name: string): boolean {\n if (this.customEvaluators[name]) {\n delete this.customEvaluators[name];\n return true;\n }\n return false;\n }\n\n /**\n * Get all registered evaluator names\n */\n getRegisteredEvaluators(): string[] {\n return Object.keys(this.customEvaluators);\n }\n\n /**\n * Internal logging helper\n */\n private log(message: string): void {\n if (this.enableLogging) {\n console.log(`[ConditionEvaluator] ${message}`);\n }\n }\n}","import type { ConversationState, IStorageAdapter } from \"../types\";\n\n/**\n * Abstract base class for storage adapters\n * Extend this class to create custom storage implementations\n */\nexport abstract class StorageAdapter implements IStorageAdapter {\n /**\n * Retrieve a conversation state by ID\n * @param conversationId - Unique conversation identifier\n * @returns The conversation state or null if not found\n */\n abstract get(conversationId: string): Promise<ConversationState | null>;\n\n /**\n * Save or update a conversation state\n * @param state - The conversation state to save\n */\n abstract save(state: ConversationState): Promise<void>;\n\n /**\n * Delete a conversation state\n * @param conversationId - Unique conversation identifier\n */\n abstract delete(conversationId: string): Promise<void>;\n\n /**\n * Check if a conversation exists\n * @param conversationId - Unique conversation identifier\n * @returns True if the conversation exists\n */\n abstract exists(conversationId: string): Promise<boolean>;\n\n /**\n * Helper method to create a deep copy of state\n * Useful for preventing mutation issues\n */\n protected deepCopy<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n }\n\n /**\n * Helper method to validate conversation state structure\n */\n protected validateState(state: ConversationState): boolean {\n return !!(\n state.conversationId &&\n state.flowId &&\n state.currentNodeId &&\n state.status &&\n Array.isArray(state.answers) &&\n Array.isArray(state.messages) &&\n state.startedAt &&\n state.updatedAt\n );\n }\n}","import type { ConversationState } from \"../types\";\nimport { StorageAdapter } from \"./StorageAdapter\";\n\n/**\n * In-memory storage adapter\n * Useful for development, testing, and single-instance deployments\n * \n * Note: Data is lost when the process restarts\n */\nexport class MemoryStorage extends StorageAdapter {\n private store: Map<string, ConversationState>;\n private maxSize: number;\n\n /**\n * Create a new MemoryStorage instance\n * @param maxSize - Maximum number of conversations to store (0 = unlimited)\n */\n constructor(maxSize: number = 0) {\n super();\n this.store = new Map();\n this.maxSize = maxSize;\n }\n\n /**\n * Retrieve a conversation state by ID\n */\n async get(conversationId: string): Promise<ConversationState | null> {\n const state = this.store.get(conversationId);\n \n if (!state) {\n return null;\n }\n\n // Return a deep copy to prevent external mutation\n return this.deepCopy(state);\n }\n\n /**\n * Save or update a conversation state\n */\n async save(state: ConversationState): Promise<void> {\n if (!this.validateState(state)) {\n throw new Error('Invalid conversation state structure');\n }\n\n // Enforce max size by removing oldest entries\n if (this.maxSize > 0 && !this.store.has(state.conversationId)) {\n while (this.store.size >= this.maxSize) {\n const oldestKey = this.findOldestEntry();\n if (oldestKey) {\n this.store.delete(oldestKey);\n }\n }\n }\n\n // Store a deep copy to prevent external mutation\n this.store.set(state.conversationId, this.deepCopy(state));\n }\n\n /**\n * Delete a conversation state\n */\n async delete(conversationId: string): Promise<void> {\n this.store.delete(conversationId);\n }\n\n /**\n * Check if a conversation exists\n */\n async exists(conversationId: string): Promise<boolean> {\n return this.store.has(conversationId);\n }\n\n /**\n * Get the current number of stored conversations\n */\n getSize(): number {\n return this.store.size;\n }\n\n /**\n * Clear all stored conversations\n */\n clear(): void {\n this.store.clear();\n }\n\n /**\n * Get all conversation IDs (useful for debugging)\n */\n getAllIds(): string[] {\n return Array.from(this.store.keys());\n }\n\n /**\n * Find the oldest conversation entry based on updatedAt\n */\n private findOldestEntry(): string | null {\n let oldestKey: string | null = null;\n let oldestDate: Date | null = null;\n\n for (const [key, state] of this.store.entries()) {\n const updatedAt = new Date(state.updatedAt);\n if (!oldestDate || updatedAt < oldestDate) {\n oldestDate = updatedAt;\n oldestKey = key;\n }\n }\n\n return oldestKey;\n }\n}","import { ConversationManager } from '../conversation/ConversationManager';\nimport { RuleProcessor } from '../rules/RuleProcessor';\nimport { TemplateEngine } from './TemplateEngine';\nimport { EventEmitter } from './EventEmitter';\nimport { ConditionEvaluator } from './ConditionEvaluator';\nimport { StorageAdapter } from '../storage/StorageAdapter';\nimport { MemoryStorage } from '../storage/MemoryStorage';\nimport type {\n AnswerOption, ChatEngineConfig, ChatEventType, ConditionEvaluatorFunction,\n ConversationFlow, ConversationMessage, ConversationNode, ConversationState,\n EvaluatorFunction, EventHandler, GotoAction, GotoResult, MessageProcessor, ProcessingResult,\n UserAnswer, ValidatorFunction, VariableResolver\n} from '../types';\n\n/**\n * Extended processing result that includes filtered options\n */\nexport interface ExtendedProcessingResult extends ProcessingResult {\n processedMessage?: string;\n filteredOptions?: AnswerOption[];\n jumpedTo?: string; // If a jump occurred, the target node ID\n}\n\n/**\n * Start conversation result with filtered options\n */\nexport interface StartConversationResult {\n state: ConversationState;\n firstNode: ConversationNode;\n processedMessage: string;\n filteredOptions?: AnswerOption[];\n}\n\n/**\n * ChatEngine is the main entry point for the chatbot core\n * It orchestrates conversation management, rule processing, event handling,\n * conditional content evaluation, and goto/jump functionality\n */\nexport class ChatEngine {\n private flows: Map<string, ConversationFlow>;\n private ruleProcessors: Map<string, RuleProcessor>;\n private conversationManager: ConversationManager;\n private storage: StorageAdapter;\n private templateEngine: TemplateEngine;\n private eventEmitter: EventEmitter;\n private conditionEvaluator: ConditionEvaluator;\n private messageProcessor?: MessageProcessor;\n private customValidators: Record<string, ValidatorFunction>;\n private customEvaluators: Record<string, EvaluatorFunction>;\n private enableLogging: boolean;\n\n constructor(config: ChatEngineConfig = {}, storage?: StorageAdapter) {\n this.storage = storage || new MemoryStorage();\n this.flows = new Map();\n this.ruleProcessors = new Map();\n this.customValidators = config.customValidators || {};\n this.customEvaluators = config.customEvaluators || {};\n this.enableLogging = config.enableLogging || false;\n this.messageProcessor = config.messageProcessor;\n\n // Initialize template engine with custom resolvers\n this.templateEngine = new TemplateEngine(config.variableResolvers || {});\n\n // Initialize event emitter with initial handlers\n this.eventEmitter = new EventEmitter(config.eventHandlers, this.enableLogging);\n\n // Initialize condition evaluator with custom evaluators\n this.conditionEvaluator = new ConditionEvaluator(\n config.conditionEvaluators || {},\n this.enableLogging\n );\n\n this.conversationManager = new ConversationManager(\n this.storage,\n this.customValidators\n );\n\n // Register default flow if provided\n if (config.defaultFlow) {\n this.registerFlow(config.defaultFlow);\n }\n }\n\n /**\n * Process a message template with variables\n */\n async processMessage(\n message: string,\n state: ConversationState,\n node: ConversationNode\n ): Promise<string> {\n let processed = await this.templateEngine.processTemplate(message, state, node);\n\n if (this.messageProcessor) {\n processed = await this.messageProcessor(processed, state, node);\n }\n\n return processed;\n }\n\n /**\n * Get the appropriate message for a node (considering conditional messages)\n * and process it through the template engine\n */\n async getProcessedMessage(\n node: ConversationNode,\n state: ConversationState\n ): Promise<string> {\n const message = await this.conditionEvaluator.getConditionalMessage(node, state);\n return this.processMessage(message, state, node);\n }\n\n /**\n * Get filtered options for a node based on display conditions\n */\n async getFilteredOptions(\n node: ConversationNode,\n state: ConversationState\n ): Promise<AnswerOption[]> {\n if (!node.options || node.options.length === 0) {\n return [];\n }\n\n return this.conditionEvaluator.filterOptions(node.options, state);\n }\n\n /**\n * Register a conversation flow\n */\n registerFlow(flow: ConversationFlow): void {\n const processor = new RuleProcessor(flow, this.customEvaluators, this.enableLogging);\n const validation = processor.validateFlow();\n\n if (!validation.valid) {\n throw new Error(\n `Invalid flow '${flow.id}': ${validation.errors.join(', ')}`\n );\n }\n\n this.flows.set(flow.id, flow);\n this.ruleProcessors.set(flow.id, processor);\n\n this.log(`Flow registered: ${flow.id} (${flow.name})`);\n }\n\n /**\n * Unregister a conversation flow\n */\n unregisterFlow(flowId: string): boolean {\n const deleted = this.flows.delete(flowId);\n this.ruleProcessors.delete(flowId);\n return deleted;\n }\n\n /**\n * Get a registered flow\n */\n getFlow(flowId: string): ConversationFlow | undefined {\n return this.flows.get(flowId);\n }\n\n /**\n * Get all registered flow IDs\n */\n getRegisteredFlowIds(): string[] {\n return Array.from(this.flows.keys());\n }\n\n /**\n * Start a new conversation\n */\n async startConversation(\n conversationId: string,\n flowId: string,\n initialContext: Record<string, unknown> = {}\n ): Promise<StartConversationResult> {\n const flow = this.flows.get(flowId);\n if (!flow) {\n throw new Error(`Flow not found: ${flowId}`);\n }\n\n const processor = this.ruleProcessors.get(flowId)!;\n const startNode = processor.getStartNode();\n\n if (!startNode) {\n throw new Error(`Start node not found for flow: ${flowId}`);\n }\n\n try {\n await this.conversationManager.createConversation(\n conversationId,\n flowId,\n startNode.id,\n initialContext\n );\n\n const stateForProcessing = await this.conversationManager.getConversation(conversationId) as ConversationState;\n\n // Check for onEnter actions\n const actionResult = await this.processNodeActions(\n conversationId,\n startNode,\n 'onEnter',\n stateForProcessing\n );\n\n // If an action caused a jump, return the new node\n if (actionResult.jumped && actionResult.node) {\n const jumpedState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n const processedMessage = await this.getProcessedMessage(actionResult.node, jumpedState);\n const filteredOptions = await this.getFilteredOptions(actionResult.node, jumpedState);\n\n const processedNode: ConversationNode = {\n ...actionResult.node,\n message: processedMessage,\n };\n\n const state = await this.conversationManager.addBotMessage(conversationId, processedNode);\n\n await this.eventEmitter.emit('onConversationStart', {\n conversationId,\n timestamp: new Date(),\n flowId,\n firstNode: actionResult.node,\n initialContext,\n });\n\n return {\n state,\n firstNode: actionResult.node,\n processedMessage,\n filteredOptions,\n };\n }\n\n const processedMessage = await this.getProcessedMessage(startNode, stateForProcessing);\n const filteredOptions = await this.getFilteredOptions(startNode, stateForProcessing);\n\n const processedNode: ConversationNode = {\n ...startNode,\n message: processedMessage,\n };\n\n const state = await this.conversationManager.addBotMessage(conversationId, processedNode);\n\n this.log(`Conversation started: ${conversationId} with flow ${flowId}`);\n\n await this.eventEmitter.emit('onConversationStart', {\n conversationId,\n timestamp: new Date(),\n flowId,\n firstNode: startNode,\n initialContext,\n });\n\n return {\n state,\n firstNode: startNode,\n processedMessage,\n filteredOptions,\n };\n } catch (error) {\n await this.eventEmitter.emit('onError', {\n conversationId,\n timestamp: new Date(),\n error: error instanceof Error ? error : new Error(String(error)),\n context: 'startConversation',\n });\n\n throw error;\n }\n }\n\n /**\n * Process user input and advance the conversation\n */\n async processInput(\n conversationId: string,\n value?: string,\n selectedOptions?: string[],\n metadata?: Record<string, unknown>\n ): Promise<ExtendedProcessingResult> {\n const state = await this.conversationManager.getConversation(conversationId);\n if (!state) {\n return {\n success: false,\n nextNode: null,\n validationError: `Conversation not found: ${conversationId}`,\n isComplete: false,\n context: {},\n };\n }\n\n if (state.status !== 'active') {\n return {\n success: false,\n nextNode: null,\n validationError: `Conversation is ${state.status}`,\n isComplete: state.status === 'completed',\n context: state.context,\n };\n }\n\n const processor = this.ruleProcessors.get(state.flowId);\n if (!processor) {\n return {\n success: false,\n nextNode: null,\n validationError: `Flow not found: ${state.flowId}`,\n isComplete: false,\n context: state.context,\n };\n }\n\n const currentNode = processor.getNode(state.currentNodeId);\n if (!currentNode) {\n return {\n success: false,\n nextNode: null,\n validationError: `Current node not found: ${state.currentNodeId}`,\n isComplete: false,\n context: state.context,\n };\n }\n\n const filteredOptions = await this.getFilteredOptions(currentNode, state);\n\n // Validate selected options against filtered options\n if (selectedOptions && selectedOptions.length > 0 && filteredOptions.length > 0) {\n const validOptionIds = new Set(filteredOptions.map((o) => o.value));\n const invalidSelections = selectedOptions.filter((id) => !validOptionIds.has(id));\n\n if (invalidSelections.length > 0) {\n this.log(`Invalid option selection: ${invalidSelections.join(', ')}`);\n\n await this.eventEmitter.emit('onValidationError', {\n conversationId,\n timestamp: new Date(),\n node: currentNode,\n error: `Invalid options selected: ${invalidSelections.join(', ')}`,\n value,\n selectedOptions,\n });\n\n return {\n success: false,\n nextNode: currentNode,\n validationError: `Invalid options selected: ${invalidSelections.join(', ')}`,\n isComplete: false,\n context: state.context,\n filteredOptions,\n };\n }\n }\n\n const validation = this.conversationManager.validateInput(\n currentNode,\n value,\n selectedOptions,\n state\n );\n\n if (!validation.valid) {\n this.log(`Validation failed for ${conversationId}: ${validation.error}`);\n\n await this.eventEmitter.emit('onValidationError', {\n conversationId,\n timestamp: new Date(),\n node: currentNode,\n error: validation.error || 'Validation failed',\n value,\n selectedOptions,\n });\n\n return {\n success: false,\n nextNode: currentNode,\n validationError: validation.error,\n isComplete: false,\n context: state.context,\n filteredOptions,\n };\n }\n\n await this.conversationManager.recordUserAnswer(\n conversationId,\n currentNode.id,\n value,\n selectedOptions,\n metadata\n );\n\n const answer: UserAnswer = {\n nodeId: currentNode.id,\n value,\n selectedOptions,\n timestamp: new Date(),\n metadata,\n };\n\n const updatedState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n\n await this.eventEmitter.emit('onAnswerRecorded', {\n conversationId,\n timestamp: new Date(),\n node: currentNode,\n answer,\n state: updatedState,\n });\n\n // Check for onExit actions on current node\n const exitActionResult = await this.processNodeActions(\n conversationId,\n currentNode,\n 'onExit',\n updatedState\n );\n\n // If exit action caused a jump, handle it\n if (exitActionResult.jumped && exitActionResult.node) {\n return this.handleJumpResult(\n conversationId,\n currentNode,\n exitActionResult.node,\n answer,\n updatedState\n );\n }\n\n // Determine next node via transitions\n const nextNode = processor.getNextNode(currentNode.id, answer, updatedState);\n\n if (!nextNode) {\n this.log(`No next node found for ${conversationId}, marking as error`);\n\n await this.eventEmitter.emit('onError', {\n conversationId,\n timestamp: new Date(),\n error: new Error('No matching transition found'),\n context: 'processInput - no next node',\n });\n\n await this.conversationManager.updateStatus(conversationId, 'error');\n return {\n success: false,\n nextNode: null,\n validationError: 'No matching transition found',\n isComplete: false,\n context: updatedState.context,\n };\n }\n\n const shouldContinue = await this.eventEmitter.emit('onBeforeTransition', {\n conversationId,\n timestamp: new Date(),\n fromNode: currentNode,\n toNode: nextNode,\n answer,\n state: updatedState,\n });\n\n if (!shouldContinue) {\n this.log(`Transition cancelled for ${conversationId}: ${currentNode.id} -> ${nextNode.id}`);\n return {\n success: false,\n nextNode: currentNode,\n validationError: 'Transition cancelled',\n isComplete: false,\n context: updatedState.context,\n filteredOptions,\n };\n }\n\n await this.conversationManager.updateCurrentNode(conversationId, nextNode.id);\n\n // Check for onEnter actions on next node\n const stateBeforeEnter = await this.conversationManager.getConversation(conversationId) as ConversationState;\n const enterActionResult = await this.processNodeActions(\n conversationId,\n nextNode,\n 'onEnter',\n stateBeforeEnter\n );\n\n // If enter action caused a jump, handle it\n if (enterActionResult.jumped && enterActionResult.node) {\n return this.handleJumpResult(\n conversationId,\n currentNode,\n enterActionResult.node,\n answer,\n stateBeforeEnter,\n nextNode.id\n );\n }\n\n const stateForProcessing = await this.conversationManager.getConversation(conversationId) as ConversationState;\n const processedMessage = await this.getProcessedMessage(nextNode, stateForProcessing);\n const nextFilteredOptions = await this.getFilteredOptions(nextNode, stateForProcessing);\n\n const processedNode: ConversationNode = {\n ...nextNode,\n message: processedMessage,\n };\n\n await this.conversationManager.addBotMessage(conversationId, processedNode);\n\n const isComplete = nextNode.type === 'end';\n if (isComplete) {\n await this.conversationManager.updateStatus(conversationId, 'completed');\n this.log(`Conversation completed: ${conversationId}`);\n\n const finalState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n await this.eventEmitter.emit('onConversationEnd', {\n conversationId,\n timestamp: new Date(),\n flowId: state.flowId,\n finalNode: nextNode,\n totalAnswers: finalState.answers.length,\n state: finalState,\n });\n }\n\n const finalState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n\n await this.eventEmitter.emit('onAfterTransition', {\n conversationId,\n timestamp: new Date(),\n fromNode: currentNode,\n toNode: nextNode,\n answer,\n state: finalState,\n processedMessage,\n });\n\n this.log(`Processed input for ${conversationId}: ${currentNode.id} -> ${nextNode.id}`);\n\n return {\n success: true,\n nextNode,\n processedMessage,\n filteredOptions: nextFilteredOptions,\n isComplete,\n context: finalState.context,\n };\n }\n\n /**\n * Jump/goto a specific node in the conversation\n */\n async goto(\n conversationId: string,\n action: GotoAction\n ): Promise<GotoResult> {\n const { targetNodeId, preserveHistory = true, reason } = action;\n\n const state = await this.conversationManager.getConversation(conversationId);\n if (!state) {\n return {\n success: false,\n previousNodeId: null,\n currentNodeId: '',\n node: null,\n error: `Conversation not found: ${conversationId}`,\n };\n }\n\n const processor = this.ruleProcessors.get(state.flowId);\n if (!processor) {\n return {\n success: false,\n previousNodeId: state.currentNodeId,\n currentNodeId: state.currentNodeId,\n node: null,\n error: `Flow not found: ${state.flowId}`,\n };\n }\n\n // Validate target node exists\n if (!processor.nodeExists(targetNodeId)) {\n return {\n success: false,\n previousNodeId: state.currentNodeId,\n currentNodeId: state.currentNodeId,\n node: null,\n error: `Target node not found: ${targetNodeId}`,\n };\n }\n\n const targetNode = processor.getNode(targetNodeId);\n if (!targetNode) {\n return {\n success: false,\n previousNodeId: state.currentNodeId,\n currentNodeId: state.currentNodeId,\n node: null,\n error: `Failed to get target node: ${targetNodeId}`,\n };\n }\n\n const previousNodeId = state.currentNodeId;\n\n try {\n // Update current node\n await this.conversationManager.updateCurrentNode(conversationId, targetNodeId);\n\n // Record jump in history\n const jumpRecord = {\n fromNodeId: previousNodeId,\n toNodeId: targetNodeId,\n reason,\n timestamp: new Date(),\n };\n\n const currentState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n const existingJumpHistory = (currentState.context.jumpHistory as Array<{\n fromNodeId: string;\n toNodeId: string;\n reason?: string;\n timestamp: Date;\n }>) || [];\n\n const updatedJumpHistory = [...existingJumpHistory, jumpRecord];\n\n await this.conversationManager.updateContext(conversationId, { jumpHistory: updatedJumpHistory });\n\n // Re-fetch state after update\n const updatedState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n\n // Get processed message and options\n const processedMessage = await this.getProcessedMessage(targetNode, updatedState);\n const filteredOptions = await this.getFilteredOptions(targetNode, updatedState);\n\n // Add bot message if preserving history\n if (preserveHistory) {\n const processedNode: ConversationNode = {\n ...targetNode,\n message: processedMessage,\n };\n await this.conversationManager.addBotMessage(conversationId, processedNode);\n }\n\n this.log(`Goto executed: ${previousNodeId} -> ${targetNodeId} (reason: ${reason || 'none'})`);\n\n // Check for onEnter actions on target node\n const stateAfterJump = await this.conversationManager.getConversation(conversationId) as ConversationState;\n const enterActionResult = await this.processNodeActions(\n conversationId,\n targetNode,\n 'onEnter',\n stateAfterJump\n );\n\n // If enter action caused another jump, return that result\n if (enterActionResult.jumped && enterActionResult.node) {\n const finalState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n const finalProcessedMessage = await this.getProcessedMessage(enterActionResult.node, finalState);\n const finalFilteredOptions = await this.getFilteredOptions(enterActionResult.node, finalState);\n\n return {\n success: true,\n previousNodeId,\n currentNodeId: enterActionResult.node.id,\n node: enterActionResult.node,\n processedMessage: finalProcessedMessage,\n filteredOptions: finalFilteredOptions,\n };\n }\n\n // Handle completion if target is an end node\n if (targetNode.type === 'end') {\n await this.conversationManager.updateStatus(conversationId, 'completed');\n\n const finalState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n await this.eventEmitter.emit('onConversationEnd', {\n conversationId,\n timestamp: new Date(),\n flowId: state.flowId,\n finalNode: targetNode,\n totalAnswers: finalState.answers.length,\n state: finalState,\n });\n }\n\n return {\n success: true,\n previousNodeId,\n currentNodeId: targetNodeId,\n node: targetNode,\n processedMessage,\n filteredOptions,\n };\n } catch (error) {\n await this.eventEmitter.emit('onError', {\n conversationId,\n timestamp: new Date(),\n error: error instanceof Error ? error : new Error(String(error)),\n context: `goto: ${previousNodeId} -> ${targetNodeId}`,\n });\n\n return {\n success: false,\n previousNodeId,\n currentNodeId: state.currentNodeId,\n node: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n /**\n * Process node actions (onEnter, onExit)\n */\n private async processNodeActions(\n conversationId: string,\n node: ConversationNode,\n actionType: 'onEnter' | 'onExit',\n state: ConversationState\n ): Promise<{ jumped: boolean; node: ConversationNode | null }> {\n const actions = node.actions?.[actionType];\n\n if (!actions || actions.length === 0) {\n return { jumped: false, node: null };\n }\n\n const processor = this.ruleProcessors.get(state.flowId);\n if (!processor) {\n return { jumped: false, node: null };\n }\n\n for (const action of actions) {\n // Check conditions if present\n if (action.conditions && action.conditions.length > 0) {\n const conditionsMet = processor.evaluateConditionsWithoutAnswer(\n action.conditions,\n state,\n action.conditionLogic || 'and'\n );\n\n if (!conditionsMet) {\n continue;\n }\n }\n\n // Execute action\n switch (action.type) {\n case 'goto':\n if (action.targetNodeId) {\n const targetNode = processor.getNode(action.targetNodeId);\n if (targetNode) {\n this.log(`Action ${actionType}: goto ${action.targetNodeId}`);\n\n await this.conversationManager.updateCurrentNode(conversationId, action.targetNodeId);\n\n // Record jump in context\n const currentState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n const existingJumpHistory = (currentState.context.jumpHistory as Array<{\n fromNodeId: string;\n toNodeId: string;\n reason?: string;\n timestamp: Date;\n }>) || [];\n\n const updatedJumpHistory = [...existingJumpHistory, {\n fromNodeId: node.id,\n toNodeId: action.targetNodeId,\n reason: `${actionType} action`,\n timestamp: new Date(),\n }];\n\n await this.conversationManager.updateContext(conversationId, { jumpHistory: updatedJumpHistory });\n\n return { jumped: true, node: targetNode };\n }\n }\n break;\n\n case 'restart':\n this.log(`Action ${actionType}: restart`);\n const flow = this.flows.get(state.flowId);\n if (flow) {\n const startNode = processor.getStartNode();\n if (startNode) {\n await this.conversationManager.resetConversation(conversationId, flow.startNodeId);\n return { jumped: true, node: startNode };\n }\n }\n break;\n\n case 'end':\n this.log(`Action ${actionType}: end`);\n await this.conversationManager.updateStatus(conversationId, 'completed');\n break;\n\n case 'pause':\n this.log(`Action ${actionType}: pause`);\n await this.conversationManager.updateStatus(conversationId, 'paused');\n break;\n }\n }\n\n return { jumped: false, node: null };\n }\n\n /**\n * Handle the result of a jump operation during input processing\n */\n private async handleJumpResult(\n conversationId: string,\n fromNode: ConversationNode,\n toNode: ConversationNode,\n answer: UserAnswer,\n state: ConversationState,\n intermediateNodeId?: string\n ): Promise<ExtendedProcessingResult> {\n const stateForProcessing = await this.conversationManager.getConversation(conversationId) as ConversationState;\n const processedMessage = await this.getProcessedMessage(toNode, stateForProcessing);\n const filteredOptions = await this.getFilteredOptions(toNode, stateForProcessing);\n\n const processedNode: ConversationNode = {\n ...toNode,\n message: processedMessage,\n };\n\n await this.conversationManager.addBotMessage(conversationId, processedNode);\n\n const isComplete = toNode.type === 'end';\n if (isComplete) {\n await this.conversationManager.updateStatus(conversationId, 'completed');\n\n const finalState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n await this.eventEmitter.emit('onConversationEnd', {\n conversationId,\n timestamp: new Date(),\n flowId: state.flowId,\n finalNode: toNode,\n totalAnswers: finalState.answers.length,\n state: finalState,\n });\n }\n\n const finalState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n\n await this.eventEmitter.emit('onAfterTransition', {\n conversationId,\n timestamp: new Date(),\n fromNode,\n toNode,\n answer,\n state: finalState,\n processedMessage,\n });\n\n this.log(`Jump completed: ${fromNode.id} -> ${toNode.id}${intermediateNodeId ? ` (via ${intermediateNodeId})` : ''}`);\n\n return {\n success: true,\n nextNode: toNode,\n processedMessage,\n filteredOptions,\n isComplete,\n context: finalState.context,\n jumpedTo: toNode.id,\n };\n }\n\n /**\n * Get the current node for a conversation with filtered options\n */\n async getCurrentNode(conversationId: string): Promise<{\n node: ConversationNode | null;\n filteredOptions?: AnswerOption[];\n processedMessage?: string;\n }> {\n const state = await this.conversationManager.getConversation(conversationId);\n if (!state) {\n return { node: null };\n }\n\n const processor = this.ruleProcessors.get(state.flowId);\n if (!processor) {\n return { node: null };\n }\n\n const node = processor.getNode(state.currentNodeId);\n if (!node) {\n return { node: null };\n }\n\n const filteredOptions = await this.getFilteredOptions(node, state);\n const processedMessage = await this.getProcessedMessage(node, state);\n\n return {\n node,\n filteredOptions,\n processedMessage,\n };\n }\n\n /**\n * Get conversation state\n */\n async getConversationState(conversationId: string): Promise<ConversationState | null> {\n return this.conversationManager.getConversation(conversationId);\n }\n\n /**\n * Get message history for a conversation\n */\n async getMessageHistory(conversationId: string): Promise<ConversationMessage[]> {\n return this.conversationManager.getMessageHistory(conversationId);\n }\n\n /**\n * Get all answers for a conversation\n */\n async getAnswers(conversationId: string): Promise<UserAnswer[]> {\n return this.conversationManager.getAllAnswers(conversationId);\n }\n\n /**\n * Get a specific answer by node ID\n */\n async getAnswer(conversationId: string, nodeId: string): Promise<UserAnswer | null> {\n return this.conversationManager.getAnswer(conversationId, nodeId);\n }\n\n /**\n * Get jump history for a conversation\n */\n async getJumpHistory(conversationId: string): Promise<Array<{\n fromNodeId: string;\n toNodeId: string;\n reason?: string;\n timestamp: Date;\n }>> {\n const state = await this.conversationManager.getConversation(conversationId);\n if (!state) return [];\n\n return (state.context.jumpHistory as Array<{\n fromNodeId: string;\n toNodeId: string;\n reason?: string;\n timestamp: Date;\n }>) || [];\n }\n\n /**\n * Update conversation context\n */\n async updateContext(\n conversationId: string,\n context: Record<string, unknown>,\n merge: boolean = true\n ): Promise<ConversationState | null> {\n try {\n return await this.conversationManager.updateContext(conversationId, context, merge);\n } catch {\n return null;\n }\n }\n\n /**\n * Pause a conversation\n */\n async pauseConversation(conversationId: string): Promise<boolean> {\n try {\n const state = await this.conversationManager.getConversation(conversationId);\n if (!state) {\n return false;\n }\n\n await this.conversationManager.updateStatus(conversationId, 'paused');\n this.log(`Conversation paused: ${conversationId}`);\n\n const processor = this.ruleProcessors.get(state.flowId);\n const currentNode = processor?.getNode(state.currentNodeId);\n\n if (currentNode) {\n const updatedState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n\n await this.eventEmitter.emit('onConversationPaused', {\n conversationId,\n timestamp: new Date(),\n currentNode,\n state: updatedState,\n });\n }\n\n return true;\n } catch (error) {\n await this.eventEmitter.emit('onError', {\n conversationId,\n timestamp: new Date(),\n error: error instanceof Error ? error : new Error(String(error)),\n context: 'pauseConversation',\n });\n\n return false;\n }\n }\n\n /**\n * Resume a paused conversation\n */\n async resumeConversation(conversationId: string): Promise<{\n node: ConversationNode | null;\n filteredOptions?: AnswerOption[];\n processedMessage?: string;\n }> {\n try {\n const state = await this.conversationManager.getConversation(conversationId);\n if (!state || state.status !== 'paused') {\n return { node: null };\n }\n\n await this.conversationManager.updateStatus(conversationId, 'active');\n this.log(`Conversation resumed: ${conversationId}`);\n\n const processor = this.ruleProcessors.get(state.flowId);\n const currentNode = processor?.getNode(state.currentNodeId) || null;\n\n if (currentNode) {\n const updatedState = await this.conversationManager.getConversation(conversationId) as ConversationState;\n\n await this.eventEmitter.emit('onConversationResumed', {\n conversationId,\n timestamp: new Date(),\n currentNode,\n state: updatedState,\n });\n\n const filteredOptions = await this.getFilteredOptions(currentNode, updatedState);\n const processedMessage = await this.getProcessedMessage(currentNode, updatedState);\n\n return {\n node: currentNode,\n filteredOptions,\n processedMessage,\n };\n }\n\n return { node: currentNode };\n } catch (error) {\n await this.eventEmitter.emit('onError', {\n conversationId,\n timestamp: new Date(),\n error: error instanceof Error ? error : new Error(String(error)),\n context: 'resumeConversation',\n });\n\n return { node: null };\n }\n }\n\n /**\n * Reset a conversation to the beginning\n */\n async resetConversation(conversationId: string): Promise<{\n node: ConversationNode | null;\n filteredOptions?: AnswerOption[];\n processedMessage?: string;\n }> {\n try {\n const state = await this.conversationManager.getConversation(conversationId);\n if (!state) {\n return { node: null };\n }\n\n const flow = this.flows.get(state.flowId);\n if (!flow) {\n return { node: null };\n }\n\n const previousAnswersCount = state.answers.length;\n\n await this.conversationManager.resetConversation(conversationId, flow.startNodeId);\n\n const processor = this.ruleProcessors.get(state.flowId)!;\n const startNode = processor.getStartNode();\n\n if (startNode) {\n const stateForProcessing = await this.conversationManager.getConversation(conversationId) as ConversationState;\n const processedMessage = await this.getProcessedMessage(startNode, stateForProcessing);\n const filteredOptions = await this.getFilteredOptions(startNode, stateForProcessing);\n\n const processedNode: ConversationNode = {\n ...startNode,\n message: processedMessage,\n };\n\n await this.conversationManager.addBotMessage(conversationId, processedNode);\n\n await this.eventEmitter.emit('onConversationReset', {\n conversationId,\n timestamp: new Date(),\n flowId: state.flowId,\n firstNode: startNode,\n previousAnswersCount,\n });\n\n this.log(`Conversation reset: ${conversationId}`);\n\n return {\n node: startNode,\n filteredOptions,\n processedMessage,\n };\n }\n\n return { node: startNode };\n } catch (error) {\n await this.eventEmitter.emit('onError', {\n conversationId,\n timestamp: new Date(),\n error: error instanceof Error ? error : new Error(String(error)),\n context: 'resetConversation',\n });\n\n return { node: null };\n }\n }\n\n /**\n * Delete a conversation\n */\n async deleteConversation(conversationId: string): Promise<boolean> {\n try {\n await this.conversationManager.deleteConversation(conversationId);\n this.log(`Conversation deleted: ${conversationId}`);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Check if a conversation exists\n */\n async conversationExists(conversationId: string): Promise<boolean> {\n return this.conversationManager.conversationExists(conversationId);\n }\n\n /**\n * Register a custom validator\n */\n registerValidator(name: string, validator: ValidatorFunction): void {\n this.customValidators[name] = validator;\n this.conversationManager.registerValidator(name, validator);\n }\n\n /**\n * Register a custom evaluator for rule processing\n */\n registerEvaluator(name: string, evaluator: EvaluatorFunction): void {\n this.customEvaluators[name] = evaluator;\n for (const processor of this.ruleProcessors.values()) {\n processor.registerEvaluator(name, evaluator);\n }\n }\n\n /**\n * Register a custom variable resolver for templates\n */\n registerVariableResolver(name: string, resolver: VariableResolver): void {\n this.templateEngine.registerResolver(name, resolver);\n }\n\n /**\n * Register a custom condition evaluator\n */\n registerConditionEvaluator(name: string, evaluator: ConditionEvaluatorFunction): void {\n this.conditionEvaluator.registerEvaluator(name, evaluator);\n }\n\n // ============================================\n // Event System Methods\n // ============================================\n\n on<T extends ChatEventType>(\n eventType: T,\n handler: EventHandler<T>\n ): () => void {\n return this.eventEmitter.on(eventType, handler);\n }\n\n once<T extends ChatEventType>(\n eventType: T,\n handler: EventHandler<T>\n ): () => void {\n return this.eventEmitter.once(eventType, handler);\n }\n\n off<T extends ChatEventType>(\n eventType: T,\n handler?: EventHandler<T>\n ): void {\n this.eventEmitter.off(eventType, handler);\n }\n\n getEventEmitter(): EventEmitter {\n return this.eventEmitter;\n }\n\n getTemplateEngine(): TemplateEngine {\n return this.templateEngine;\n }\n\n getConditionEvaluator(): ConditionEvaluator {\n return this.conditionEvaluator;\n }\n\n getStorage(): StorageAdapter {\n return this.storage;\n }\n\n getConversationManager(): ConversationManager {\n return this.conversationManager;\n }\n\n /**\n * Internal logging helper\n */\n private log(message: string): void {\n if (this.enableLogging) {\n console.log(`[ChatEngine] ${message}`);\n }\n }\n}"],"names":["byteToHex","i","push","toString","slice","getRandomValues","rnds8","Uint8Array","native","randomUUID","crypto","bind","_v4","options","buf","offset","rnds","random","rng","Error","length","arr","toLowerCase","unsafeStringify","v4","ConversationManager","constructor","storage","customValidators","this","createConversation","conversationId","flowId","startNodeId","initialContext","get","now","Date","state","currentNodeId","status","answers","messages","context","startedAt","updatedAt","save","getConversation","conversationExists","exists","addBotMessage","node","getConversationOrThrow","message","id","uuidv4","role","content","nodeId","timestamp","metadata","recordUserAnswer","value","selectedOptions","answer","messageContent","formatAnswerAsMessage","validateInput","validation","valid","required","hasValue","trim","hasSelection","error","minLength","maxLength","pattern","RegExp","test","console","warn","customValidator","validator","result","validOptionIds","Set","map","o","invalidOptions","filter","has","join","updateCurrentNode","updateStatus","updateContext","merge","getAnswer","a","getAllAnswers","getMessageHistory","deleteConversation","delete","resetConversation","registerValidator","name","RuleProcessor","flow","customEvaluators","enableLogging","getNode","nodes","find","getStartNode","nodeExists","some","getAllNodeIds","getNextNode","transitions","t","fromNodeId","sort","b","priority","log","transition","evaluateTransition","toNodeId","conditions","conditionLogic","condition","evaluateCondition","every","logic","evaluateNestedConditions","evaluateSimpleCondition","toUpperCase","subCondition","field","operator","customEvaluator","evaluator","actualValue","getFieldValue","expectedValue","compareValues","sourceId","getNestedValue","getStateValue","obj","path","parts","split","current","part","JSON","stringify","property","actual","expected","normalizeValue","checkContains","compareNumeric","Array","isArray","v","includes","String","expectedStr","compareFn","actualNum","Number","expectedNum","isNaN","validateFlow","errors","nodeIds","n","validateConditions","type","getPossibleNextNodes","nextNodes","evaluateConditionsWithoutAnswer","dummyAnswer","registerEvaluator","setFlow","getFlow","TemplateEngine","customResolvers","processTemplate","template","matches","matchAll","match","fullMatch","variablePath","resolveVariable","stringValue","formatValue","replace","resolverName","args","resolver","resolveBuiltIn","getMostRecentAnswer","simpleAnswer","toLocaleString","toLocaleDateString","toLocaleTimeString","registerResolver","hasVariables","EventEmitter","initialHandlers","handlers","Map","registerHandlers","eventTypes","Object","keys","eventType","handler","set","handlersArray","h","on","off","once","wrappedHandler","payload","index","indexOf","splice","emit","isCancellable","hasHandlers","handlerCount","getRegisteredEvents","from","removeAllHandlers","clear","ConditionEvaluator","filterOptions","filteredOptions","option","displayConditions","evaluateConditions","getConditionalMessage","conditionalMessages","sortedMessages","conditionalMessage","shouldDisplayNode","isSingleCondition","evaluateSingleCondition","isConditionGroup","evaluateConditionGroup","group","source","evaluateCustomCondition","getSourceValue","getAnswerValue","getContextValue","getMetadataValue","key","evaluatorName","unregisterEvaluator","getRegisteredEvaluators","StorageAdapter","deepCopy","parse","validateState","MemoryStorage","maxSize","super","store","size","oldestKey","findOldestEntry","getSize","getAllIds","oldestDate","entries","ChatEngine","config","flows","ruleProcessors","messageProcessor","templateEngine","variableResolvers","eventEmitter","eventHandlers","conditionEvaluator","conditionEvaluators","conversationManager","defaultFlow","registerFlow","processMessage","processed","getProcessedMessage","getFilteredOptions","processor","unregisterFlow","deleted","getRegisteredFlowIds","startConversation","startNode","stateForProcessing","actionResult","processNodeActions","jumped","jumpedState","processedMessage","processedNode","firstNode","processInput","success","nextNode","validationError","isComplete","currentNode","invalidSelections","updatedState","exitActionResult","handleJumpResult","fromNode","toNode","stateBeforeEnter","enterActionResult","nextFilteredOptions","finalState","finalNode","totalAnswers","goto","action","targetNodeId","preserveHistory","reason","previousNodeId","targetNode","jumpRecord","currentState","updatedJumpHistory","jumpHistory","stateAfterJump","finalProcessedMessage","finalFilteredOptions","actionType","actions","intermediateNodeId","jumpedTo","getCurrentNode","getConversationState","getAnswers","getJumpHistory","pauseConversation","resumeConversation","previousAnswersCount","values","registerVariableResolver","registerConditionEvaluator","getEventEmitter","getTemplateEngine","getConditionEvaluator","getStorage","getConversationManager"],"mappings":"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","x_google_ignoreList":[0,1,2,3]}
@@ -0,0 +1,2 @@
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self)["rule-based-chat"]={})}(this,function(t){"use strict";const e=[];for(let m=0;m<256;++m)e.push((m+256).toString(16).slice(1));let n;const s=new Uint8Array(16);const o={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function r(t,o,r){const a=(t=t||{}).random??t.rng?.()??function(){if(!n){if("undefined"==typeof crypto||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");n=crypto.getRandomValues.bind(crypto)}return n(s)}();if(a.length<16)throw new Error("Random bytes length must be >= 16");return a[6]=15&a[6]|64,a[8]=63&a[8]|128,function(t,n=0){return(e[t[n+0]]+e[t[n+1]]+e[t[n+2]]+e[t[n+3]]+"-"+e[t[n+4]]+e[t[n+5]]+"-"+e[t[n+6]]+e[t[n+7]]+"-"+e[t[n+8]]+e[t[n+9]]+"-"+e[t[n+10]]+e[t[n+11]]+e[t[n+12]]+e[t[n+13]]+e[t[n+14]]+e[t[n+15]]).toLowerCase()}(a)}function a(t,e,n){return o.randomUUID&&!t?o.randomUUID():r(t)}class i{constructor(t,e={}){this.storage=t,this.customValidators=e}async createConversation(t,e,n,s={}){if(await this.storage.get(t))throw new Error(`Conversation with ID '${t}' already exists`);const o=new Date,r={conversationId:t,flowId:e,currentNodeId:n,status:"active",answers:[],messages:[],context:s,startedAt:o,updatedAt:o};return await this.storage.save(r),r}async getConversation(t){return this.storage.get(t)}async conversationExists(t){return this.storage.exists(t)}async addBotMessage(t,e){const n=await this.getConversationOrThrow(t),s={id:a(),role:"bot",content:e.message,nodeId:e.id,timestamp:new Date,metadata:e.metadata};return n.messages.push(s),n.updatedAt=new Date,await this.storage.save(n),n}async recordUserAnswer(t,e,n,s,o){const r=await this.getConversationOrThrow(t),i={nodeId:e,value:n,selectedOptions:s,timestamp:new Date,metadata:o};r.answers.push(i);const d=this.formatAnswerAsMessage(n,s),l={id:a(),role:"user",content:d,nodeId:e,timestamp:new Date,metadata:o};return r.messages.push(l),r.updatedAt=new Date,await this.storage.save(r),r}validateInput(t,e,n,s){const o=t.validation;if(!o)return{valid:!0};if(o.required){const t=void 0!==e&&""!==e.trim(),s=void 0!==n&&n.length>0;if(!t&&!s)return{valid:!1,error:"This field is required"}}if(void 0!==e){if(void 0!==o.minLength&&e.length<o.minLength)return{valid:!1,error:`Minimum length is ${o.minLength} characters`};if(void 0!==o.maxLength&&e.length>o.maxLength)return{valid:!1,error:`Maximum length is ${o.maxLength} characters`};if(o.pattern)try{if(!new RegExp(o.pattern).test(e))return{valid:!1,error:"Invalid format"}}catch{console.warn(`Invalid validation pattern: ${o.pattern}`)}}if(o.customValidator){const r=this.customValidators[o.customValidator];if(r){const o=r(e||"",{nodeId:t.id,value:e,selectedOptions:n,timestamp:new Date},s);if("string"==typeof o)return{valid:!1,error:o};if(!o)return{valid:!1,error:"Validation failed"}}else console.warn(`Custom validator not found: ${o.customValidator}`)}if(n&&t.options){const e=new Set(t.options.map(t=>t.value)),s=n.filter(t=>!e.has(t));if(s.length>0)return{valid:!1,error:`Invalid options selected: ${s.join(", ")}`}}return{valid:!0}}async updateCurrentNode(t,e){const n=await this.getConversationOrThrow(t);return n.currentNodeId=e,n.updatedAt=new Date,await this.storage.save(n),n}async updateStatus(t,e){const n=await this.getConversationOrThrow(t);return n.status=e,n.updatedAt=new Date,await this.storage.save(n),n}async updateContext(t,e,n=!0){const s=await this.getConversationOrThrow(t);return s.context=n?{...s.context,...e}:e,s.updatedAt=new Date,await this.storage.save(s),s}async getAnswer(t,e){const n=await this.storage.get(t);if(!n)return null;const s=n.answers.filter(t=>t.nodeId===e);return s.length>0?s[s.length-1]:null}async getAllAnswers(t){const e=await this.storage.get(t);return e?.answers||[]}async getMessageHistory(t){const e=await this.storage.get(t);return e?.messages||[]}async deleteConversation(t){await this.storage.delete(t)}async resetConversation(t,e){const n=await this.getConversationOrThrow(t);return n.currentNodeId=e,n.status="active",n.answers=[],n.messages=[],n.updatedAt=new Date,await this.storage.save(n),n}registerValidator(t,e){this.customValidators[t]=e}async getConversationOrThrow(t){const e=await this.storage.get(t);if(!e)throw new Error(`Conversation not found: ${t}`);return e}formatAnswerAsMessage(t,e){return void 0!==t&&""!==t?t:e&&e.length>0?e.join(", "):""}}class d{constructor(t,e={},n=!1){this.flow=t,this.customEvaluators=e,this.enableLogging=n}getNode(t){return this.flow.nodes.find(e=>e.id===t)||null}getStartNode(){return this.getNode(this.flow.startNodeId)}nodeExists(t){return this.flow.nodes.some(e=>e.id===t)}getAllNodeIds(){return this.flow.nodes.map(t=>t.id)}getNextNode(t,e,n){const s=this.flow.transitions.filter(e=>e.fromNodeId===t).sort((t,e)=>(e.priority||0)-(t.priority||0));this.log(`Evaluating ${s.length} transitions from '${t}'`);for(const o of s){if(this.evaluateTransition(o,e,n))return this.log(`Transition matched: ${t} -> ${o.toNodeId}`),this.getNode(o.toNodeId)}return this.log(`No matching transition found from '${t}'`),null}evaluateTransition(t,e,n){if(!t.conditions||0===t.conditions.length)return!0;return"or"===(t.conditionLogic||"and")?t.conditions.some(t=>this.evaluateCondition(t,e,n)):t.conditions.every(t=>this.evaluateCondition(t,e,n))}evaluateCondition(t,e,n){return t.logic&&t.conditions&&t.conditions.length>0?this.evaluateNestedConditions(t,e,n):this.evaluateSimpleCondition(t,e,n)}evaluateNestedConditions(t,e,n){const{logic:s,conditions:o}=t;if(!o||0===o.length)return!0;if(this.log(`Evaluating nested ${s?.toUpperCase()} condition group with ${o.length} conditions`),"or"===s){for(const t of o)if(this.evaluateCondition(t,e,n))return this.log("OR condition group: found true condition"),!0;return!1}for(const r of o)if(!this.evaluateCondition(r,e,n))return this.log("AND condition group: found false condition"),!1;return!0}evaluateSimpleCondition(t,e,n){const{field:s,operator:o}=t;if("always"===o)return!0;if("custom"===s&&t.customEvaluator){const s=this.customEvaluators[t.customEvaluator];if(s){const o=s(e,n,t);return this.log(`Custom evaluator '${t.customEvaluator}' returned: ${o}`),o}return this.log(`Custom evaluator not found: ${t.customEvaluator}`),!1}const r=this.getFieldValue(t,e,n),a=t.value;if("exists"===o)return null!=r&&""!==r;if("not_exists"===o)return null==r||""===r;const i=this.compareValues(o,r,a);return this.log(`Condition: ${s}[${t.sourceId||""}] ${o} ${a} => ${i} (actual: ${r})`),i}getFieldValue(t,e,n){const{field:s,sourceId:o}=t;switch(s){case"value":default:return e.value;case"selectedOptions":return e.selectedOptions;case"context":if(!o)return;return this.getNestedValue(n.context,o);case"state":if(!o)return;return this.getStateValue(n,o)}}getNestedValue(t,e){const n=e.split(".");let s=t;for(const o of n){if(null==s)return;if("object"!=typeof s)return;s=s[o]}if(null!=s)return"object"==typeof s?JSON.stringify(s):s}getStateValue(t,e){switch(e){case"status":return t.status;case"answersCount":return t.answers.length;case"messagesCount":return t.messages.length;case"flowId":return t.flowId;case"currentNodeId":return t.currentNodeId;case"conversationId":return t.conversationId;default:return}}compareValues(t,e,n){switch(t){case"equals":return this.normalizeValue(e)===this.normalizeValue(n);case"not_equals":return this.normalizeValue(e)!==this.normalizeValue(n);case"contains":return this.checkContains(e,n);case"not_contains":return!this.checkContains(e,n);case"matches":if("string"!=typeof e||"string"!=typeof n)return!1;try{return new RegExp(n,"i").test(e)}catch{return this.log(`Invalid regex pattern: ${n}`),!1}case"greater_than":return this.compareNumeric(e,n,(t,e)=>t>e);case"less_than":return this.compareNumeric(e,n,(t,e)=>t<e);case"greater_than_or_equals":return this.compareNumeric(e,n,(t,e)=>t>=e);case"less_than_or_equals":return this.compareNumeric(e,n,(t,e)=>t<=e);case"in":return!!Array.isArray(n)&&n.map(t=>this.normalizeValue(t)).includes(this.normalizeValue(e));case"not_in":return!Array.isArray(n)||!n.map(t=>this.normalizeValue(t)).includes(this.normalizeValue(e));case"exists":return null!=e&&""!==e;case"not_exists":return null==e||""===e;case"always":return!0;default:return this.log(`Unknown operator: ${t}`),!1}}normalizeValue(t){return null==t?"":Array.isArray(t)?t.map(t=>String(t).toLowerCase().trim()).sort().join(","):String(t).toLowerCase().trim()}checkContains(t,e){if(null==t)return!1;const n=String(e).toLowerCase();return Array.isArray(t)?t.some(t=>String(t).toLowerCase().includes(n)):String(t).toLowerCase().includes(n)}compareNumeric(t,e,n){const s=Number(t),o=Number(e);return!isNaN(s)&&!isNaN(o)&&n(s,o)}validateFlow(){const t=[],e=new Set(this.flow.nodes.map(t=>t.id));e.has(this.flow.startNodeId)||t.push(`Start node '${this.flow.startNodeId}' does not exist`);for(const n of this.flow.transitions)e.has(n.fromNodeId)||t.push(`Transition references non-existent fromNodeId: '${n.fromNodeId}'`),e.has(n.toNodeId)||t.push(`Transition references non-existent toNodeId: '${n.toNodeId}'`),n.conditions&&this.validateConditions(n.conditions,t);for(const n of this.flow.nodes){if("end"===n.type)continue;this.flow.transitions.some(t=>t.fromNodeId===n.id)||t.push(`Node '${n.id}' has no outgoing transitions`)}return{valid:0===t.length,errors:t}}validateConditions(t,e){for(const n of t)n.conditions&&n.conditions.length>0?(n.logic||e.push("Nested condition group missing logic (and/or)"),this.validateConditions(n.conditions,e)):(n.operator||"custom"===n.field||e.push("Condition missing operator"),"custom"!==n.field||n.customEvaluator||e.push("Custom condition missing evaluator name"))}getPossibleNextNodes(t){const e=this.flow.transitions.filter(e=>e.fromNodeId===t),n=[];for(const s of e){const t=this.getNode(s.toNodeId);t&&!n.find(e=>e.id===t.id)&&n.push(t)}return n}evaluateConditionsWithoutAnswer(t,e,n="and"){const s={nodeId:e.currentNodeId,timestamp:new Date};return"or"===n?t.some(t=>this.evaluateCondition(t,s,e)):t.every(t=>this.evaluateCondition(t,s,e))}registerEvaluator(t,e){this.customEvaluators[t]=e}setFlow(t){this.flow=t}getFlow(){return this.flow}log(t){this.enableLogging&&console.log(`[RuleProcessor] ${t}`)}}class l{constructor(t={}){this.customResolvers=t}async processTemplate(t,e,n){const s=t.matchAll(/\{\{([^}]+)\}\}/g);let o=t;for(const r of s){const t=r[0],s=r[1].trim(),a=await this.resolveVariable(s,e,n),i=this.formatValue(a);o=o.replace(t,i)}return o}async resolveVariable(t,e,n){if(t.includes(":")){const[s,...o]=t.split(":"),r=this.customResolvers[s];if(r)return r(o.join(":"),e,n)}return this.customResolvers[t]?this.customResolvers[t](t,e,n):this.resolveBuiltIn(t,e,n)}resolveBuiltIn(t,e,n){const s=t.split(".");if("context"===s[0])return this.getNestedValue(e.context,s.slice(1));if("answer"===s[0]&&s.length>=2){const t=s[1],n=this.getMostRecentAnswer(e,t);return n?2===s.length?n.value||n.selectedOptions?.join(", ")||null:"value"===s[2]?n.value||null:"selectedOptions"===s[2]?n.selectedOptions?.join(", ")||null:"timestamp"===s[2]?n.timestamp.toString():null:null}const o=this.getMostRecentAnswer(e,t);return o?o.value||o.selectedOptions?.join(", ")||null:"conversationId"===t?e.conversationId:"flowId"===t?e.flowId:"currentNodeId"===t?e.currentNodeId:"status"===t?e.status:"answersCount"===t?e.answers.length:"messagesCount"===t?e.messages.length:"node"===s[0]?"id"===s[1]?n.id:"type"===s[1]?n.type||"text":"message"===s[1]?n.message:null:"now"===t?(new Date).toLocaleString():"today"===t?(new Date).toLocaleDateString():"time"===t?(new Date).toLocaleTimeString():null}getMostRecentAnswer(t,e){for(let n=t.answers.length-1;n>=0;n--)if(t.answers[n].nodeId===e)return t.answers[n];return null}getNestedValue(t,e){let n=t;for(const s of e){if(null==n)return null;if("object"!=typeof n)return null;n=n[s]}return null==n?null:"object"==typeof n?JSON.stringify(n):n}formatValue(t){return null==t?"[unknown]":String(t)}registerResolver(t,e){this.customResolvers[t]=e}hasVariables(t){return/\{\{[^}]+\}\}/.test(t)}}class u{constructor(t,e=!1){this.handlers=new Map,this.enableLogging=e,t&&this.registerHandlers(t)}registerHandlers(t){const e=Object.keys(t);for(const n of e){const e=t[n];if(!e)continue;this.handlers.has(n)||this.handlers.set(n,[]);const s=this.handlers.get(n);if(Array.isArray(e))for(const t of e)s.push(t);else s.push(e);this.log(`Handler(s) registered for event: ${n}`)}}on(t,e){this.handlers.has(t)||this.handlers.set(t,[]);return this.handlers.get(t).push(e),this.log(`Handler registered for event: ${t}`),()=>{this.off(t,e)}}once(t,e){const n=s=>(this.off(t,n),e(s));return this.on(t,n)}off(t,e){if(!e)return this.handlers.delete(t),void this.log(`All handlers removed for event: ${t}`);const n=this.handlers.get(t);if(!n)return;const s=n.indexOf(e);-1!==s&&(n.splice(s,1),this.log(`Handler removed for event: ${t}`)),0===n.length&&this.handlers.delete(t)}async emit(t,e){const n=this.handlers.get(t);if(!n||0===n.length)return this.log(`No handlers for event: ${t}`),!0;this.log(`Emitting event: ${t} to ${n.length} handler(s)`);const s="onBeforeTransition"===t;for(const r of n)try{const n=await r(e);if(s&&!1===n)return this.log(`Event ${t} cancelled by handler`),!1}catch(o){console.error(`Error in event handler for ${t}:`,o),"onError"!==t&&await this.emit("onError",{conversationId:e.conversationId||"unknown",timestamp:new Date,error:o instanceof Error?o:new Error(String(o)),context:`Error in ${t} handler`})}return!0}hasHandlers(t){const e=this.handlers.get(t);return void 0!==e&&e.length>0}handlerCount(t){return this.handlers.get(t)?.length||0}getRegisteredEvents(){return Array.from(this.handlers.keys())}removeAllHandlers(){this.handlers.clear(),this.log("All event handlers removed")}log(t){this.enableLogging&&console.log(`[EventEmitter] ${t}`)}}class c{constructor(t={},e=!1){this.customEvaluators=t,this.enableLogging=e}async filterOptions(t,e){const n=[];for(const s of t){if(!s.displayConditions){n.push(s);continue}await this.evaluateConditions(s.displayConditions,e)?n.push(s):this.log(`Option '${s.value}' hidden by display conditions`)}return n}async getConditionalMessage(t,e){if(!t.conditionalMessages||0===t.conditionalMessages.length)return t.message;const n=[...t.conditionalMessages].sort((t,e)=>(e.priority||0)-(t.priority||0));for(const s of n){if(await this.evaluateConditions(s.conditions,e))return this.log(`Using conditional message for node '${t.id}'`),s.message}return t.message}async shouldDisplayNode(t,e){return!t.displayConditions||this.evaluateConditions(t.displayConditions,e)}async evaluateConditions(t,e){if(this.isSingleCondition(t))return this.evaluateSingleCondition(t,e);if(Array.isArray(t)){for(const n of t){if(!(await this.evaluateConditions(n,e)))return!1}return!0}return this.isConditionGroup(t)?this.evaluateConditionGroup(t,e):(this.log("Unknown condition format, defaulting to true"),!0)}async evaluateConditionGroup(t,e){const{logic:n,conditions:s}=t;if("and"===n){for(const t of s){if(!(await this.evaluateConditions(t,e)))return!1}return!0}if("or"===n){for(const t of s){if(await this.evaluateConditions(t,e))return!0}return!1}return!0}async evaluateSingleCondition(t,e){const{source:n,operator:s}=t;if("custom"===n||"custom"===s)return this.evaluateCustomCondition(t,e);const o=this.getSourceValue(t,e);return"exists"===s?null!=o:"not_exists"===s?null==o:this.compareValues(s,o,t.value)}getSourceValue(t,e){const{source:n,sourceId:s,field:o}=t;switch(n){case"answer":return this.getAnswerValue(e,s,o);case"context":return this.getContextValue(e,s,o);case"state":return this.getStateValue(e,s);case"metadata":return this.getMetadataValue(e,s,o);default:return null}}getAnswerValue(t,e,n){if(!e)return null;const s=this.getMostRecentAnswer(t,e);if(!s)return null;if(!n)return s.value??s.selectedOptions??null;switch(n){case"value":return s.value;case"selectedOptions":return s.selectedOptions;case"timestamp":return s.timestamp;default:return s.metadata?.[n]??null}}getMostRecentAnswer(t,e){for(let n=t.answers.length-1;n>=0;n--)if(t.answers[n].nodeId===e)return t.answers[n];return null}getContextValue(t,e,n){if(!e)return null;const s=t.context[e];return n&&"object"==typeof s&&null!==s?s[n]??null:s??null}getStateValue(t,e){if(!e)return null;switch(e){case"status":return t.status;case"answersCount":return t.answers.length;case"messagesCount":return t.messages.length;case"flowId":return t.flowId;case"currentNodeId":return t.currentNodeId;case"conversationId":return t.conversationId;default:return null}}getMetadataValue(t,e,n){if(!e)return null;const s=this.getMostRecentAnswer(t,e);return s?.metadata?n?s.metadata[n]??null:s.metadata:null}async evaluateCustomCondition(t,e){const n=t.customEvaluator;if(!n)return this.log("Custom condition missing evaluator name"),!1;const s=this.customEvaluators[n];if(!s)return this.log(`Custom condition evaluator not found: ${n}`),!1;try{return await s(e,t)}catch(o){return this.log(`Error in custom evaluator '${n}': ${o}`),!1}}compareValues(t,e,n){switch(t){case"equals":return this.normalizeValue(e)===this.normalizeValue(n);case"not_equals":return this.normalizeValue(e)!==this.normalizeValue(n);case"contains":return this.checkContains(e,n);case"not_contains":return!this.checkContains(e,n);case"in":return!!Array.isArray(n)&&n.map(t=>this.normalizeValue(t)).includes(this.normalizeValue(e));case"not_in":return!Array.isArray(n)||!n.map(t=>this.normalizeValue(t)).includes(this.normalizeValue(e));case"greater_than":return this.compareNumeric(e,n,(t,e)=>t>e);case"less_than":return this.compareNumeric(e,n,(t,e)=>t<e);case"greater_than_or_equals":return this.compareNumeric(e,n,(t,e)=>t>=e);case"less_than_or_equals":return this.compareNumeric(e,n,(t,e)=>t<=e);case"matches":if("string"!=typeof e||"string"!=typeof n)return!1;try{return new RegExp(n,"i").test(e)}catch{return this.log(`Invalid regex pattern: ${n}`),!1}case"exists":return null!=e;case"not_exists":return null==e;default:return!1}}normalizeValue(t){return null==t?"":Array.isArray(t)?t.map(t=>String(t).toLowerCase().trim()).sort().join(","):String(t).toLowerCase().trim()}checkContains(t,e){if(null==t)return!1;const n=String(e).toLowerCase();return Array.isArray(t)?t.some(t=>String(t).toLowerCase().includes(n)):String(t).toLowerCase().includes(n)}compareNumeric(t,e,n){const s=Number(t),o=Number(e);return!isNaN(s)&&!isNaN(o)&&n(s,o)}isSingleCondition(t){return"object"==typeof t&&!Array.isArray(t)&&"source"in t&&"operator"in t}isConditionGroup(t){return"object"==typeof t&&!Array.isArray(t)&&"logic"in t&&"conditions"in t}registerEvaluator(t,e){this.customEvaluators[t]=e,this.log(`Custom condition evaluator registered: ${t}`)}unregisterEvaluator(t){return!!this.customEvaluators[t]&&(delete this.customEvaluators[t],!0)}getRegisteredEvaluators(){return Object.keys(this.customEvaluators)}log(t){this.enableLogging&&console.log(`[ConditionEvaluator] ${t}`)}}class g{deepCopy(t){return JSON.parse(JSON.stringify(t))}validateState(t){return!!(t.conversationId&&t.flowId&&t.currentNodeId&&t.status&&Array.isArray(t.answers)&&Array.isArray(t.messages)&&t.startedAt&&t.updatedAt)}}class h extends g{constructor(t=0){super(),this.store=new Map,this.maxSize=t}async get(t){const e=this.store.get(t);return e?this.deepCopy(e):null}async save(t){if(!this.validateState(t))throw new Error("Invalid conversation state structure");if(this.maxSize>0&&!this.store.has(t.conversationId))for(;this.store.size>=this.maxSize;){const t=this.findOldestEntry();t&&this.store.delete(t)}this.store.set(t.conversationId,this.deepCopy(t))}async delete(t){this.store.delete(t)}async exists(t){return this.store.has(t)}getSize(){return this.store.size}clear(){this.store.clear()}getAllIds(){return Array.from(this.store.keys())}findOldestEntry(){let t=null,e=null;for(const[n,s]of this.store.entries()){const o=new Date(s.updatedAt);(!e||o<e)&&(e=o,t=n)}return t}}t.ChatEngine=class{constructor(t={},e){this.storage=e||new h,this.flows=new Map,this.ruleProcessors=new Map,this.customValidators=t.customValidators||{},this.customEvaluators=t.customEvaluators||{},this.enableLogging=t.enableLogging||!1,this.messageProcessor=t.messageProcessor,this.templateEngine=new l(t.variableResolvers||{}),this.eventEmitter=new u(t.eventHandlers,this.enableLogging),this.conditionEvaluator=new c(t.conditionEvaluators||{},this.enableLogging),this.conversationManager=new i(this.storage,this.customValidators),t.defaultFlow&&this.registerFlow(t.defaultFlow)}async processMessage(t,e,n){let s=await this.templateEngine.processTemplate(t,e,n);return this.messageProcessor&&(s=await this.messageProcessor(s,e,n)),s}async getProcessedMessage(t,e){const n=await this.conditionEvaluator.getConditionalMessage(t,e);return this.processMessage(n,e,t)}async getFilteredOptions(t,e){return t.options&&0!==t.options.length?this.conditionEvaluator.filterOptions(t.options,e):[]}registerFlow(t){const e=new d(t,this.customEvaluators,this.enableLogging),n=e.validateFlow();if(!n.valid)throw new Error(`Invalid flow '${t.id}': ${n.errors.join(", ")}`);this.flows.set(t.id,t),this.ruleProcessors.set(t.id,e),this.log(`Flow registered: ${t.id} (${t.name})`)}unregisterFlow(t){const e=this.flows.delete(t);return this.ruleProcessors.delete(t),e}getFlow(t){return this.flows.get(t)}getRegisteredFlowIds(){return Array.from(this.flows.keys())}async startConversation(t,e,n={}){if(!this.flows.get(e))throw new Error(`Flow not found: ${e}`);const s=this.ruleProcessors.get(e).getStartNode();if(!s)throw new Error(`Start node not found for flow: ${e}`);try{await this.conversationManager.createConversation(t,e,s.id,n);const o=await this.conversationManager.getConversation(t),r=await this.processNodeActions(t,s,"onEnter",o);if(r.jumped&&r.node){const s=await this.conversationManager.getConversation(t),o=await this.getProcessedMessage(r.node,s),a=await this.getFilteredOptions(r.node,s),i={...r.node,message:o},d=await this.conversationManager.addBotMessage(t,i);return await this.eventEmitter.emit("onConversationStart",{conversationId:t,timestamp:new Date,flowId:e,firstNode:r.node,initialContext:n}),{state:d,firstNode:r.node,processedMessage:o,filteredOptions:a}}const a=await this.getProcessedMessage(s,o),i=await this.getFilteredOptions(s,o),d={...s,message:a},l=await this.conversationManager.addBotMessage(t,d);return this.log(`Conversation started: ${t} with flow ${e}`),await this.eventEmitter.emit("onConversationStart",{conversationId:t,timestamp:new Date,flowId:e,firstNode:s,initialContext:n}),{state:l,firstNode:s,processedMessage:a,filteredOptions:i}}catch(o){throw await this.eventEmitter.emit("onError",{conversationId:t,timestamp:new Date,error:o instanceof Error?o:new Error(String(o)),context:"startConversation"}),o}}async processInput(t,e,n,s){const o=await this.conversationManager.getConversation(t);if(!o)return{success:!1,nextNode:null,validationError:`Conversation not found: ${t}`,isComplete:!1,context:{}};if("active"!==o.status)return{success:!1,nextNode:null,validationError:`Conversation is ${o.status}`,isComplete:"completed"===o.status,context:o.context};const r=this.ruleProcessors.get(o.flowId);if(!r)return{success:!1,nextNode:null,validationError:`Flow not found: ${o.flowId}`,isComplete:!1,context:o.context};const a=r.getNode(o.currentNodeId);if(!a)return{success:!1,nextNode:null,validationError:`Current node not found: ${o.currentNodeId}`,isComplete:!1,context:o.context};const i=await this.getFilteredOptions(a,o);if(n&&n.length>0&&i.length>0){const s=new Set(i.map(t=>t.value)),r=n.filter(t=>!s.has(t));if(r.length>0)return this.log(`Invalid option selection: ${r.join(", ")}`),await this.eventEmitter.emit("onValidationError",{conversationId:t,timestamp:new Date,node:a,error:`Invalid options selected: ${r.join(", ")}`,value:e,selectedOptions:n}),{success:!1,nextNode:a,validationError:`Invalid options selected: ${r.join(", ")}`,isComplete:!1,context:o.context,filteredOptions:i}}const d=this.conversationManager.validateInput(a,e,n,o);if(!d.valid)return this.log(`Validation failed for ${t}: ${d.error}`),await this.eventEmitter.emit("onValidationError",{conversationId:t,timestamp:new Date,node:a,error:d.error||"Validation failed",value:e,selectedOptions:n}),{success:!1,nextNode:a,validationError:d.error,isComplete:!1,context:o.context,filteredOptions:i};await this.conversationManager.recordUserAnswer(t,a.id,e,n,s);const l={nodeId:a.id,value:e,selectedOptions:n,timestamp:new Date,metadata:s},u=await this.conversationManager.getConversation(t);await this.eventEmitter.emit("onAnswerRecorded",{conversationId:t,timestamp:new Date,node:a,answer:l,state:u});const c=await this.processNodeActions(t,a,"onExit",u);if(c.jumped&&c.node)return this.handleJumpResult(t,a,c.node,l,u);const g=r.getNextNode(a.id,l,u);if(!g)return this.log(`No next node found for ${t}, marking as error`),await this.eventEmitter.emit("onError",{conversationId:t,timestamp:new Date,error:new Error("No matching transition found"),context:"processInput - no next node"}),await this.conversationManager.updateStatus(t,"error"),{success:!1,nextNode:null,validationError:"No matching transition found",isComplete:!1,context:u.context};if(!(await this.eventEmitter.emit("onBeforeTransition",{conversationId:t,timestamp:new Date,fromNode:a,toNode:g,answer:l,state:u})))return this.log(`Transition cancelled for ${t}: ${a.id} -> ${g.id}`),{success:!1,nextNode:a,validationError:"Transition cancelled",isComplete:!1,context:u.context,filteredOptions:i};await this.conversationManager.updateCurrentNode(t,g.id);const h=await this.conversationManager.getConversation(t),m=await this.processNodeActions(t,g,"onEnter",h);if(m.jumped&&m.node)return this.handleJumpResult(t,a,m.node,l,h,g.id);const v=await this.conversationManager.getConversation(t),f=await this.getProcessedMessage(g,v),w=await this.getFilteredOptions(g,v),p={...g,message:f};await this.conversationManager.addBotMessage(t,p);const C="end"===g.type;if(C){await this.conversationManager.updateStatus(t,"completed"),this.log(`Conversation completed: ${t}`);const e=await this.conversationManager.getConversation(t);await this.eventEmitter.emit("onConversationEnd",{conversationId:t,timestamp:new Date,flowId:o.flowId,finalNode:g,totalAnswers:e.answers.length,state:e})}const y=await this.conversationManager.getConversation(t);return await this.eventEmitter.emit("onAfterTransition",{conversationId:t,timestamp:new Date,fromNode:a,toNode:g,answer:l,state:y,processedMessage:f}),this.log(`Processed input for ${t}: ${a.id} -> ${g.id}`),{success:!0,nextNode:g,processedMessage:f,filteredOptions:w,isComplete:C,context:y.context}}async goto(t,e){const{targetNodeId:n,preserveHistory:s=!0,reason:o}=e,r=await this.conversationManager.getConversation(t);if(!r)return{success:!1,previousNodeId:null,currentNodeId:"",node:null,error:`Conversation not found: ${t}`};const a=this.ruleProcessors.get(r.flowId);if(!a)return{success:!1,previousNodeId:r.currentNodeId,currentNodeId:r.currentNodeId,node:null,error:`Flow not found: ${r.flowId}`};if(!a.nodeExists(n))return{success:!1,previousNodeId:r.currentNodeId,currentNodeId:r.currentNodeId,node:null,error:`Target node not found: ${n}`};const i=a.getNode(n);if(!i)return{success:!1,previousNodeId:r.currentNodeId,currentNodeId:r.currentNodeId,node:null,error:`Failed to get target node: ${n}`};const d=r.currentNodeId;try{await this.conversationManager.updateCurrentNode(t,n);const e={fromNodeId:d,toNodeId:n,reason:o,timestamp:new Date},a=await this.conversationManager.getConversation(t),l=[...a.context.jumpHistory||[],e];await this.conversationManager.updateContext(t,{jumpHistory:l});const u=await this.conversationManager.getConversation(t),c=await this.getProcessedMessage(i,u),g=await this.getFilteredOptions(i,u);if(s){const e={...i,message:c};await this.conversationManager.addBotMessage(t,e)}this.log(`Goto executed: ${d} -> ${n} (reason: ${o||"none"})`);const h=await this.conversationManager.getConversation(t),m=await this.processNodeActions(t,i,"onEnter",h);if(m.jumped&&m.node){const e=await this.conversationManager.getConversation(t),n=await this.getProcessedMessage(m.node,e),s=await this.getFilteredOptions(m.node,e);return{success:!0,previousNodeId:d,currentNodeId:m.node.id,node:m.node,processedMessage:n,filteredOptions:s}}if("end"===i.type){await this.conversationManager.updateStatus(t,"completed");const e=await this.conversationManager.getConversation(t);await this.eventEmitter.emit("onConversationEnd",{conversationId:t,timestamp:new Date,flowId:r.flowId,finalNode:i,totalAnswers:e.answers.length,state:e})}return{success:!0,previousNodeId:d,currentNodeId:n,node:i,processedMessage:c,filteredOptions:g}}catch(l){return await this.eventEmitter.emit("onError",{conversationId:t,timestamp:new Date,error:l instanceof Error?l:new Error(String(l)),context:`goto: ${d} -> ${n}`}),{success:!1,previousNodeId:d,currentNodeId:r.currentNodeId,node:null,error:l instanceof Error?l.message:String(l)}}}async processNodeActions(t,e,n,s){const o=e.actions?.[n];if(!o||0===o.length)return{jumped:!1,node:null};const r=this.ruleProcessors.get(s.flowId);if(!r)return{jumped:!1,node:null};for(const a of o){if(a.conditions&&a.conditions.length>0){if(!r.evaluateConditionsWithoutAnswer(a.conditions,s,a.conditionLogic||"and"))continue}switch(a.type){case"goto":if(a.targetNodeId){const s=r.getNode(a.targetNodeId);if(s){this.log(`Action ${n}: goto ${a.targetNodeId}`),await this.conversationManager.updateCurrentNode(t,a.targetNodeId);const o=[...(await this.conversationManager.getConversation(t)).context.jumpHistory||[],{fromNodeId:e.id,toNodeId:a.targetNodeId,reason:`${n} action`,timestamp:new Date}];return await this.conversationManager.updateContext(t,{jumpHistory:o}),{jumped:!0,node:s}}}break;case"restart":this.log(`Action ${n}: restart`);const o=this.flows.get(s.flowId);if(o){const e=r.getStartNode();if(e)return await this.conversationManager.resetConversation(t,o.startNodeId),{jumped:!0,node:e}}break;case"end":this.log(`Action ${n}: end`),await this.conversationManager.updateStatus(t,"completed");break;case"pause":this.log(`Action ${n}: pause`),await this.conversationManager.updateStatus(t,"paused")}}return{jumped:!1,node:null}}async handleJumpResult(t,e,n,s,o,r){const a=await this.conversationManager.getConversation(t),i=await this.getProcessedMessage(n,a),d=await this.getFilteredOptions(n,a),l={...n,message:i};await this.conversationManager.addBotMessage(t,l);const u="end"===n.type;if(u){await this.conversationManager.updateStatus(t,"completed");const e=await this.conversationManager.getConversation(t);await this.eventEmitter.emit("onConversationEnd",{conversationId:t,timestamp:new Date,flowId:o.flowId,finalNode:n,totalAnswers:e.answers.length,state:e})}const c=await this.conversationManager.getConversation(t);return await this.eventEmitter.emit("onAfterTransition",{conversationId:t,timestamp:new Date,fromNode:e,toNode:n,answer:s,state:c,processedMessage:i}),this.log(`Jump completed: ${e.id} -> ${n.id}${r?` (via ${r})`:""}`),{success:!0,nextNode:n,processedMessage:i,filteredOptions:d,isComplete:u,context:c.context,jumpedTo:n.id}}async getCurrentNode(t){const e=await this.conversationManager.getConversation(t);if(!e)return{node:null};const n=this.ruleProcessors.get(e.flowId);if(!n)return{node:null};const s=n.getNode(e.currentNodeId);if(!s)return{node:null};return{node:s,filteredOptions:await this.getFilteredOptions(s,e),processedMessage:await this.getProcessedMessage(s,e)}}async getConversationState(t){return this.conversationManager.getConversation(t)}async getMessageHistory(t){return this.conversationManager.getMessageHistory(t)}async getAnswers(t){return this.conversationManager.getAllAnswers(t)}async getAnswer(t,e){return this.conversationManager.getAnswer(t,e)}async getJumpHistory(t){const e=await this.conversationManager.getConversation(t);return e&&e.context.jumpHistory||[]}async updateContext(t,e,n=!0){try{return await this.conversationManager.updateContext(t,e,n)}catch{return null}}async pauseConversation(t){try{const e=await this.conversationManager.getConversation(t);if(!e)return!1;await this.conversationManager.updateStatus(t,"paused"),this.log(`Conversation paused: ${t}`);const n=this.ruleProcessors.get(e.flowId),s=n?.getNode(e.currentNodeId);if(s){const e=await this.conversationManager.getConversation(t);await this.eventEmitter.emit("onConversationPaused",{conversationId:t,timestamp:new Date,currentNode:s,state:e})}return!0}catch(e){return await this.eventEmitter.emit("onError",{conversationId:t,timestamp:new Date,error:e instanceof Error?e:new Error(String(e)),context:"pauseConversation"}),!1}}async resumeConversation(t){try{const e=await this.conversationManager.getConversation(t);if(!e||"paused"!==e.status)return{node:null};await this.conversationManager.updateStatus(t,"active"),this.log(`Conversation resumed: ${t}`);const n=this.ruleProcessors.get(e.flowId),s=n?.getNode(e.currentNodeId)||null;if(s){const e=await this.conversationManager.getConversation(t);await this.eventEmitter.emit("onConversationResumed",{conversationId:t,timestamp:new Date,currentNode:s,state:e});const n=await this.getFilteredOptions(s,e);return{node:s,filteredOptions:n,processedMessage:await this.getProcessedMessage(s,e)}}return{node:s}}catch(e){return await this.eventEmitter.emit("onError",{conversationId:t,timestamp:new Date,error:e instanceof Error?e:new Error(String(e)),context:"resumeConversation"}),{node:null}}}async resetConversation(t){try{const e=await this.conversationManager.getConversation(t);if(!e)return{node:null};const n=this.flows.get(e.flowId);if(!n)return{node:null};const s=e.answers.length;await this.conversationManager.resetConversation(t,n.startNodeId);const o=this.ruleProcessors.get(e.flowId).getStartNode();if(o){const n=await this.conversationManager.getConversation(t),r=await this.getProcessedMessage(o,n),a=await this.getFilteredOptions(o,n),i={...o,message:r};return await this.conversationManager.addBotMessage(t,i),await this.eventEmitter.emit("onConversationReset",{conversationId:t,timestamp:new Date,flowId:e.flowId,firstNode:o,previousAnswersCount:s}),this.log(`Conversation reset: ${t}`),{node:o,filteredOptions:a,processedMessage:r}}return{node:o}}catch(e){return await this.eventEmitter.emit("onError",{conversationId:t,timestamp:new Date,error:e instanceof Error?e:new Error(String(e)),context:"resetConversation"}),{node:null}}}async deleteConversation(t){try{return await this.conversationManager.deleteConversation(t),this.log(`Conversation deleted: ${t}`),!0}catch{return!1}}async conversationExists(t){return this.conversationManager.conversationExists(t)}registerValidator(t,e){this.customValidators[t]=e,this.conversationManager.registerValidator(t,e)}registerEvaluator(t,e){this.customEvaluators[t]=e;for(const n of this.ruleProcessors.values())n.registerEvaluator(t,e)}registerVariableResolver(t,e){this.templateEngine.registerResolver(t,e)}registerConditionEvaluator(t,e){this.conditionEvaluator.registerEvaluator(t,e)}on(t,e){return this.eventEmitter.on(t,e)}once(t,e){return this.eventEmitter.once(t,e)}off(t,e){this.eventEmitter.off(t,e)}getEventEmitter(){return this.eventEmitter}getTemplateEngine(){return this.templateEngine}getConditionEvaluator(){return this.conditionEvaluator}getStorage(){return this.storage}getConversationManager(){return this.conversationManager}log(t){this.enableLogging&&console.log(`[ChatEngine] ${t}`)}},t.ConditionEvaluator=c,t.ConversationManager=i,t.EventEmitter=u,t.MemoryStorage=h,t.RuleProcessor=d,t.StorageAdapter=g,t.TemplateEngine=l,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
2
+ //# sourceMappingURL=index.umd.js.map