UNPKG

cs-element

Version:

Advanced reactive data management library with state machines, blueprints, persistence, compression, networking, and multithreading support

1 lines 1.64 MB
{"version":3,"file":"index.cjs","sources":["../src/types/interfaces.ts","../src/utils/helpers.ts","../src/utils/QueryEngine.ts","../src/live-queries/LiveQueryManagerImpl.ts","../src/utils/AsyncLock.ts","../src/utils/Validator.ts","../src/types/plugin-interfaces.ts","../src/core/AdvancedMiddlewareManager.ts","../src/core/PluginManager.ts","../src/types/persistence-interfaces.ts","../src/persistence/PersistenceManagerImpl.ts","../src/history/HistoryManagerImpl.ts","../src/reactivity/ReactivityManagerImpl.ts","../src/graph/GraphAlgorithmsManagerImpl.ts","../src/core/ElementRegistry.ts","../src/core/ServiceRegistry.ts","../src/core/ElementNavigation.ts","../src/core/ElementSerializer.ts","../src/core/CSElement.ts","../src/utils/TypeScriptGenerator.ts","../src/types/batch-interfaces.ts","../src/batch/BatchManager.ts","../src/batch/BatchOperationFactory.ts","../src/blueprint/Blueprint.ts","../src/blueprint/BlueprintBuilder.ts","../src/blueprint/BlueprintManager.ts","../src/types/blueprint-interfaces.ts","../src/persistence/MemoryStorageAdapter.ts","../src/types/transaction-interfaces.ts","../src/transactions/LockManagerImpl.ts","../src/transactions/TransactionManagerImpl.ts","../src/visualization/VisualizationManager.ts","../src/visualization/engines/AsciiEngine.ts","../src/visualization/engines/SvgEngine.ts","../src/visualization/engines/HtmlEngine.ts","../src/plugins/VisualizationPlugin.ts","../src/plugins/ValidationPlugin.ts","../src/plugins/CachePlugin.ts","../src/plugins/MetricsPlugin.ts","../src/plugins/TransformPlugin.ts","../src/plugins/AnalyticsPlugin.ts","../src/devtools/ElementInspector.ts","../src/devtools/PerformanceProfiler.ts","../src/devtools/DevToolsManager.ts","../src/plugins/DevToolsPlugin.ts","../src/plugins/LoggingPlugin.ts","../src/types/state-machine-interfaces.ts","../src/state-machine/StateMachine.ts","../src/state-machine/StateMachineManager.ts","../src/core/StateMachineIntegration.ts","../src/types/typed-elements-interfaces.ts","../src/typed-elements/TypedElement.ts","../src/typed-elements/TypedElementManager.ts","../src/migration/MigrationManager.ts","../src/migration/MemoryMigrationStorage.ts","../src/migration/VersionUtils.ts","../src/migration/MigrationBuilder.ts","../src/migration/MigrationDecorators.ts","../src/migration/MigrationFactory.ts","../src/migration/MigrationRunner.ts","../src/migration/MigrationScheduler.ts","../src/migration/MigrationLogger.ts","../src/migration/MigrationSystem.ts","../src/react/hooks.ts","../src/types/worker-interfaces.ts","../src/diff/DiffEngine.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Основные типы и интерфейсы для библиотеки CSElement\r\n */\r\n\r\nexport type ElementValue = any;\r\n\r\n/**\r\n * Опции для создания элемента\r\n */\r\nexport interface ElementOptions {\r\n name?: string;\r\n data?: Map<string, any>;\r\n index?: number;\r\n}\r\n\r\n/**\r\n * Базовый интерфейс для паттерна CSElement\r\n */\r\nexport interface ICSElementPattern {\r\n readonly id: string;\r\n readonly name: string;\r\n readonly index: number;\r\n readonly data: ReadonlyMap<string, any>;\r\n readonly mainOwner: ICSElementPattern | null;\r\n \r\n getOwner(nameOrIndex: string | number): ICSElementPattern | null;\r\n getOwnerAsync(nameOrIndex: string | number): Promise<ICSElementPattern | null>;\r\n \r\n getElement(nameOrIndex: string | number): ICSElementPattern | null;\r\n getElementAsync(nameOrIndex: string | number): Promise<ICSElementPattern | null>;\r\n \r\n getRelativeIndex(owner: ICSElementPattern): number;\r\n \r\n elementsCount(): number;\r\n ownersCount(): number;\r\n \r\n getAllElements(): ReadonlyArray<ICSElementPattern>;\r\n getAllOwners(): ReadonlyArray<ICSElementPattern>;\r\n}\r\n\r\n/**\r\n * Расширенный интерфейс для работы с элементами\r\n */\r\nexport interface ICSElement extends ICSElementPattern {\r\n readonly lastElement: ICSElement | null;\r\n \r\n addElement(value: ElementValue, options?: ElementOptions): Promise<ICSElement>;\r\n removeElement(nameOrIndex: string | number | ICSElement): Promise<boolean>;\r\n \r\n setData(key: string, value: any): Promise<void>;\r\n getData(key: string): any;\r\n deleteData(key: string): Promise<boolean>;\r\n \r\n // Методы для работы с блокировками (CSElementHole)\r\n enterLock(): Promise<void>;\r\n leaveLock(): Promise<void>;\r\n isLocked(): boolean;\r\n}\r\n\r\n/**\r\n * Типы событий элемента\r\n */\r\nexport enum ElementEventType {\r\n ElementAdded = 'element:added',\r\n ElementRemoved = 'element:removed',\r\n DataChanged = 'data:changed',\r\n OwnerChanged = 'owner:changed',\r\n Locked = 'locked',\r\n Unlocked = 'unlocked'\r\n}\r\n\r\n/**\r\n * Интерфейс события элемента\r\n */\r\nexport interface ElementEvent {\r\n type: string;\r\n target: ICSElement;\r\n data?: any;\r\n timestamp: number;\r\n}\r\n\r\n// Новые интерфейсы для расширенной функциональности\r\n\r\nexport interface SerializationOptions {\r\n includeChildren?: boolean;\r\n includeData?: boolean;\r\n includeOwners?: boolean;\r\n includeMetadata?: boolean;\r\n maxDepth?: number;\r\n excludeFields?: string[];\r\n}\r\n\r\nexport interface SerializedElement {\r\n id: string;\r\n name?: string;\r\n index?: number;\r\n data?: Record<string, any>;\r\n children?: SerializedElement[];\r\n owners?: string[];\r\n metadata?: {\r\n createdAt: number;\r\n updatedAt: number;\r\n depth: number;\r\n path: string;\r\n };\r\n}\r\n\r\nexport interface ValidationRule {\r\n field: string;\r\n type: 'required' | 'type' | 'range' | 'custom' | 'schema' | 'async' | 'pattern' | 'enum' | 'array' | 'object';\r\n value?: any;\r\n validator?: (value: any, element: ICSElement) => boolean | string | Promise<boolean | string>;\r\n message?: string;\r\n}\r\n\r\nexport interface ValidationResult {\r\n isValid: boolean;\r\n errors: ValidationError[];\r\n warnings?: ValidationError[];\r\n}\r\n\r\nexport interface ValidationError {\r\n field: string;\r\n message: string;\r\n value?: any;\r\n}\r\n\r\nexport interface QuerySelector {\r\n name?: string;\r\n index?: number;\r\n hasData?: string[];\r\n depth?: number | { min?: number; max?: number };\r\n custom?: (element: ICSElement) => boolean;\r\n}\r\n\r\nexport interface PersistenceAdapter {\r\n save(elements: ICSElement[], format: 'json' | 'xml'): Promise<string>;\r\n load(data: string, format: 'json' | 'xml'): Promise<ICSElement[]>;\r\n}\r\n\r\nexport interface PerformanceMetrics {\r\n totalElements: number;\r\n averageDepth: number;\r\n maxDepth: number;\r\n memoryUsage: number;\r\n operationCounts: {\r\n create: number;\r\n read: number;\r\n update: number;\r\n delete: number;\r\n };\r\n} ","/**\r\n * Генерация уникального идентификатора\r\n */\r\nexport function generateId(): string {\r\n return `cs_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\r\n}\r\n\r\n/**\r\n * Проверка является ли значение функцией\r\n */\r\nexport function isFunction(value: any): value is Function {\r\n return typeof value === 'function';\r\n}\r\n\r\n/**\r\n * Глубокое клонирование объекта\r\n */\r\nexport function deepClone<T>(obj: T): T {\r\n if (obj === null || typeof obj !== 'object') {\r\n return obj;\r\n }\r\n\r\n if (obj instanceof Date) {\r\n return new Date(obj.getTime()) as any;\r\n }\r\n\r\n if (obj instanceof Array) {\r\n const cloneArr: any[] = [];\r\n for (let i = 0; i < obj.length; i++) {\r\n cloneArr[i] = deepClone(obj[i]);\r\n }\r\n return cloneArr as any;\r\n }\r\n\r\n if (obj instanceof Map) {\r\n const cloneMap = new Map();\r\n obj.forEach((value, key) => {\r\n cloneMap.set(key, deepClone(value));\r\n });\r\n return cloneMap as any;\r\n }\r\n\r\n if (obj instanceof Set) {\r\n const cloneSet = new Set();\r\n obj.forEach((value) => {\r\n cloneSet.add(deepClone(value));\r\n });\r\n return cloneSet as any;\r\n }\r\n\r\n const cloneObj: any = {};\r\n for (const key in obj) {\r\n if (obj.hasOwnProperty(key)) {\r\n cloneObj[key] = deepClone(obj[key]);\r\n }\r\n }\r\n\r\n return cloneObj;\r\n}\r\n\r\n/**\r\n * Задержка выполнения\r\n */\r\nexport function delay(ms: number): Promise<void> {\r\n return new Promise(resolve => setTimeout(resolve, ms));\r\n} ","/**\n * Универсальный движок запросов для CSElement\n * Поддерживает CSS-селекторы, XPath, объектные селекторы, индексирование и оптимизацию запросов\n */\n\nimport { ICSElement, QuerySelector } from '../types/interfaces';\nimport * as JSPath from 'jspath';\nimport { CSElement } from '../core/CSElement';\n\n/**\n * Типы селекторов\n */\nexport enum SelectorType {\n CSS = 'css',\n XPATH = 'xpath',\n OBJECT = 'object'\n}\n\n/**\n * CSS-селектор\n */\nexport interface CSSSelector {\n type: SelectorType.CSS;\n selector: string;\n}\n\n/**\n * XPath-селектор\n */\nexport interface XPathSelector {\n type: SelectorType.XPATH;\n expression: string;\n}\n\n/**\n * Объектный селектор\n */\nexport interface ObjectSelector {\n type: SelectorType.OBJECT;\n selector: QuerySelector;\n}\n\n/**\n * Универсальный селектор\n */\nexport type UniversalSelector = CSSSelector | XPathSelector | ObjectSelector | string;\n\n/**\n * Результат парсинга CSS-селектора\n */\ninterface ParsedCSSSelector {\n element?: string;\n id?: string;\n classes?: string[];\n attributes?: { name: string; operator?: string; value?: string }[];\n pseudoClasses?: { name: string; argument?: string }[];\n combinator?: '>' | '+' | '~' | ' ';\n next?: ParsedCSSSelector;\n}\n\n/**\n * Индекс для быстрого поиска\n */\ninterface ElementIndex {\n byName: Map<string, Set<ICSElement>>;\n byId: Map<string, ICSElement>;\n byClass: Map<string, Set<ICSElement>>;\n byAttribute: Map<string, Map<any, Set<ICSElement>>>;\n byDepth: Map<number, Set<ICSElement>>;\n all: Set<ICSElement>;\n}\n\n/**\n * Статистика запросов\n */\nexport interface QueryStats {\n totalQueries: number;\n cacheHits: number;\n averageExecutionTime: number;\n slowestQuery: {\n selector: string;\n time: number;\n };\n mostFrequentSelectors: Array<{\n selector: string;\n count: number;\n }>;\n}\n\n/**\n * Расширенный движок запросов\n */\nexport class QueryEngine {\n private static indices = new WeakMap<ICSElement, ElementIndex>();\n private static queryCache = new Map<string, { result: ICSElement[]; timestamp: number }>();\n private static cacheTimeout = 5000; // 5 секунд\n private static stats: QueryStats = {\n totalQueries: 0,\n cacheHits: 0,\n averageExecutionTime: 0,\n slowestQuery: { selector: '', time: 0 },\n mostFrequentSelectors: []\n };\n private static selectorFrequency = new Map<string, number>();\n\n /**\n * Выполняет поиск элементов по универсальному селектору\n */\n static query(root: ICSElement, selector: UniversalSelector): ICSElement[] {\n const startTime = performance.now();\n const selectorString = this.getSelectorString(selector);\n \n // Создаем ключ кэша, который включает ID корневого элемента\n const cacheKey = `${root.id}:${selectorString}`;\n \n // Проверяем кэш\n const cached = this.getCachedResult(cacheKey);\n if (cached) {\n this.stats.cacheHits++;\n this.stats.totalQueries++; // Кэшированные запросы тоже считаются как запросы\n \n // Обновляем частоту селекторов для кэшированных запросов\n const currentCount = this.selectorFrequency.get(selectorString) || 0;\n this.selectorFrequency.set(selectorString, currentCount + 1);\n \n // Обновляем топ селекторов\n this.stats.mostFrequentSelectors = Array.from(this.selectorFrequency.entries())\n .map(([sel, count]) => ({ selector: sel, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 10);\n \n return cached;\n }\n\n let result: ICSElement[];\n\n if (typeof selector === 'string') {\n // Автоматически определяем тип селектора\n if (selector.startsWith('//') || selector.startsWith('/')) {\n result = this.queryXPath(root, selector);\n } else {\n result = this.queryCSS(root, selector);\n }\n } else {\n switch (selector.type) {\n case SelectorType.CSS:\n result = this.queryCSS(root, selector.selector);\n break;\n case SelectorType.XPATH:\n result = this.queryXPath(root, selector.expression);\n break;\n case SelectorType.OBJECT:\n result = this.queryObject(root, selector.selector);\n break;\n default:\n throw new Error(`Неподдерживаемый тип селектора: ${(selector as any).type}`);\n }\n }\n\n // Обновляем статистику\n const executionTime = performance.now() - startTime;\n this.updateStats(selectorString, executionTime);\n\n // Кэшируем результат с учетом корневого элемента\n this.cacheResult(cacheKey, result);\n\n return result;\n }\n\n /**\n * Находит первый элемент по селектору\n */\n static queryOne(root: ICSElement, selector: UniversalSelector): ICSElement | null {\n const results = this.query(root, selector);\n return results.length > 0 ? results[0] : null;\n }\n\n /**\n * Выполняет поиск по CSS-селектору\n */\n static queryCSS(root: ICSElement, selector: string): ICSElement[] {\n const parsed = this.parseCSS(selector);\n return this.executeCSS(root, parsed);\n }\n\n /**\n * Выполняет поиск по XPath\n */\n static queryXPath(root: ICSElement, expression: string): ICSElement[] {\n const context = this.createXPathContext(root);\n return this.evaluateXPath(context, expression);\n }\n\n /**\n * Выполняет поиск по объектному селектору\n */\n static queryObject(root: ICSElement, selector: QuerySelector): ICSElement[] {\n return this.queryBySelector(root, selector);\n }\n\n /**\n * Включить/выключить индексирование\n */\n static setIndexingEnabled(enabled: boolean): void {\n // В данной реализации индексирование всегда включено\n // Этот метод оставлен для совместимости API\n console.log(`Индексирование ${enabled ? 'включено' : 'выключено'}`);\n }\n\n /**\n * Создает или обновляет индекс для элемента\n */\n static buildIndex(root: ICSElement): void {\n const index: ElementIndex = {\n byName: new Map(),\n byId: new Map(),\n byClass: new Map(),\n byAttribute: new Map(),\n byDepth: new Map(),\n all: new Set()\n };\n\n this.traverseAndIndex(root, index, 0);\n this.indices.set(root, index);\n }\n\n /**\n * Получает индекс для элемента\n */\n static getIndex(root: ICSElement): ElementIndex {\n let index = this.indices.get(root);\n if (!index) {\n this.buildIndex(root);\n index = this.indices.get(root)!;\n }\n return index;\n }\n\n /**\n * Очищает кэш запросов\n */\n static clearCache(): void {\n this.queryCache.clear();\n }\n\n /**\n * Получает статистику запросов\n */\n static getStats(): QueryStats {\n return { ...this.stats };\n }\n\n /**\n * Сбрасывает статистику\n */\n static resetStats(): void {\n this.stats = {\n totalQueries: 0,\n cacheHits: 0,\n averageExecutionTime: 0,\n slowestQuery: { selector: '', time: 0 },\n mostFrequentSelectors: []\n };\n this.selectorFrequency.clear();\n }\n\n /**\n * Приватные методы\n */\n\n private static getSelectorString(selector: UniversalSelector): string {\n if (typeof selector === 'string') {\n return selector;\n }\n switch (selector.type) {\n case SelectorType.CSS:\n return `css:${selector.selector}`;\n case SelectorType.XPATH:\n return `xpath:${selector.expression}`;\n case SelectorType.OBJECT:\n return `object:${JSON.stringify(selector.selector)}`;\n default:\n return 'unknown';\n }\n }\n\n private static getCachedResult(selector: string): ICSElement[] | null {\n const cached = this.queryCache.get(selector);\n if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {\n return cached.result;\n }\n if (cached) {\n this.queryCache.delete(selector);\n }\n return null;\n }\n\n private static cacheResult(selector: string, result: ICSElement[]): void {\n this.queryCache.set(selector, {\n result: [...result],\n timestamp: Date.now()\n });\n }\n\n private static updateStats(selector: string, executionTime: number): void {\n this.stats.totalQueries++;\n \n // Обновляем среднее время выполнения\n this.stats.averageExecutionTime = \n (this.stats.averageExecutionTime * (this.stats.totalQueries - 1) + executionTime) / this.stats.totalQueries;\n \n // Обновляем самый медленный запрос\n if (executionTime > this.stats.slowestQuery.time) {\n this.stats.slowestQuery = { selector, time: executionTime };\n }\n \n // Обновляем частоту селекторов\n const currentCount = this.selectorFrequency.get(selector) || 0;\n this.selectorFrequency.set(selector, currentCount + 1);\n \n // Обновляем топ селекторов\n this.stats.mostFrequentSelectors = Array.from(this.selectorFrequency.entries())\n .map(([sel, count]) => ({ selector: sel, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 10);\n }\n\n private static parseCSS(selector: string): ParsedCSSSelector {\n // Улучшенный CSS парсер\n const result: ParsedCSSSelector = {};\n \n\n \n // Убираем лишние пробелы и разбиваем по комбинаторам\n const trimmed = selector.trim();\n // Измененный regex, чтобы поддерживать селекторы, начинающиеся с комбинатора\n const combinatorMatch = trimmed.match(/^([>+~])?\\s*(.*)$/);\n \n if (!combinatorMatch) {\n this.parseSelectorPart(trimmed, result);\n return result;\n }\n\n const [, combinator, rest] = combinatorMatch;\n \n if (combinator) {\n result.combinator = combinator as any;\n const nextParts = rest.split(/\\s*([>+~])\\s*/);\n result.next = this.parseCSS(nextParts.join(' '));\n return result;\n }\n\n // Старая логика для селекторов, не начинающихся с комбинатора\n const simpleMatch = trimmed.match(/^([^>+~\\s]+)(?:\\s*([>+~]|\\s+)\\s*(.+))?$/);\n if (!simpleMatch) {\n this.parseSelectorPart(trimmed, result);\n return result;\n }\n \n const [, currentPart, simpleCombinator, simpleRest] = simpleMatch;\n this.parseSelectorPart(currentPart, result);\n\n if (simpleCombinator && simpleRest) {\n result.combinator = simpleCombinator.trim() === '' ? ' ' : simpleCombinator.trim() as any;\n result.next = this.parseCSS(simpleRest);\n }\n \n\n \n return result;\n }\n\n private static parseSelectorPart(part: string, result: ParsedCSSSelector): void {\n let remaining = part;\n\n // Парсим элемент, ID, классы, атрибуты и псевдо-классы\n while (remaining) {\n if (remaining.startsWith('#')) {\n // ID селектор\n const match = remaining.match(/^#([^.:\\[]+)/);\n if (match) {\n result.id = match[1];\n remaining = remaining.substring(match[0].length);\n } else {\n break;\n }\n } else if (remaining.startsWith('.')) {\n // Class селектор\n const match = remaining.match(/^\\.([^.:\\[#]+)/);\n if (match) {\n if (!result.classes) result.classes = [];\n result.classes.push(match[1]);\n remaining = remaining.substring(match[0].length);\n } else {\n break;\n }\n } else if (remaining.startsWith('[')) {\n // Атрибутный селектор\n const match = remaining.match(/^\\[([^=\\]]+)(?:(=|~=|\\|=|\\^=|\\$=|\\*=)\"?([^\"\\]]+)\"?)?\\]/);\n if (match) {\n if (!result.attributes) result.attributes = [];\n result.attributes.push({\n name: match[1],\n operator: match[2],\n value: match[3]\n });\n remaining = remaining.substring(match[0].length);\n } else {\n break;\n }\n } else if (remaining.startsWith(':')) {\n // Псевдо-класс\n const match = remaining.match(/^:([^:(]+)(?:\\(([^)]+)\\))?/);\n if (match) {\n if (!result.pseudoClasses) result.pseudoClasses = [];\n result.pseudoClasses.push({\n name: match[1],\n argument: match[2]\n });\n remaining = remaining.substring(match[0].length);\n } else {\n break;\n }\n } else {\n // Имя элемента (должно быть в начале)\n const match = remaining.match(/^([a-zA-Z][a-zA-Z0-9-_]*)/);\n if (match && !result.element && !result.id && !result.classes && !result.attributes && !result.pseudoClasses) {\n result.element = match[1];\n remaining = remaining.substring(match[0].length);\n } else {\n break;\n }\n }\n }\n }\n\n private static executeCSS(root: ICSElement, parsed: ParsedCSSSelector): ICSElement[] {\n // Phase 1: Find elements matching the first part of the selector.\n // If the selector part is empty (e.g., '> .foo'), the initial context is just the root.\n const isPartEmpty = !parsed.element && !parsed.id && !parsed.classes && !parsed.attributes && !parsed.pseudoClasses;\n const initialMatches = isPartEmpty ? [root] : this.findDescendantsAndSelf(root, parsed);\n\n // If there's no combinator, we're done.\n if (!parsed.combinator || !parsed.next) {\n return initialMatches;\n }\n\n // Phase 2: Apply combinator to find the next set of elements.\n const finalMatches = new Set<ICSElement>();\n for (const element of initialMatches) {\n switch (parsed.combinator) {\n case '>':\n const children = element.getAllElements() as ICSElement[];\n for (const child of children) {\n if (this.matchesParsedSelector(child, parsed.next)) {\n finalMatches.add(child);\n }\n }\n break;\n case ' ':\n const descendants = this.findDescendantsAndSelf(element, parsed.next);\n // Exclude the element itself from its descendants\n descendants.forEach(d => {\n if (d.id !== element.id) finalMatches.add(d);\n });\n break;\n case '+':\n const parentPlus = element.mainOwner;\n if (parentPlus) {\n const siblings = parentPlus.getAllElements();\n const elementIndex = siblings.findIndex(el => el.id === element.id);\n if (elementIndex > -1 && elementIndex + 1 < siblings.length) {\n const nextSibling = siblings[elementIndex + 1] as ICSElement;\n if (this.matchesParsedSelector(nextSibling, parsed.next)) {\n finalMatches.add(nextSibling);\n }\n }\n }\n break;\n case '~':\n const parentTilde = element.mainOwner;\n if (parentTilde) {\n const siblings = parentTilde.getAllElements();\n const elementIndex = siblings.findIndex(el => el.id === element.id);\n if (elementIndex > -1) {\n for (let i = elementIndex + 1; i < siblings.length; i++) {\n const nextSibling = siblings[i] as ICSElement;\n if (this.matchesParsedSelector(nextSibling, parsed.next)) {\n finalMatches.add(nextSibling);\n }\n }\n }\n }\n break;\n }\n }\n\n return Array.from(finalMatches);\n }\n\n // Helper to find all descendants (and self) that match a simple selector part\n private static findDescendantsAndSelf(root: ICSElement, parsed: ParsedCSSSelector): ICSElement[] {\n const results: ICSElement[] = [];\n \n // Проверяем сам корневой элемент\n if (this.matchesParsedSelector(root, parsed)) {\n results.push(root);\n }\n \n // Рекурсивно ищем в потомках\n const searchInChildren = (element: ICSElement) => {\n const children = element.getAllElements() as ICSElement[];\n for (const child of children) {\n if (this.matchesParsedSelector(child, parsed)) {\n results.push(child);\n }\n // Рекурсивно ищем в потомках\n searchInChildren(child);\n }\n };\n \n searchInChildren(root);\n return results;\n }\n\n private static findDescendantsByParsedSelector(root: ICSElement, parsed: ParsedCSSSelector): ICSElement[] {\n const results: ICSElement[] = [];\n \n // Рекурсивно ищем среди потомков, не включая сам root\n const searchInChildren = (element: ICSElement) => {\n const children = element.getAllElements() as ICSElement[];\n for (const child of children) {\n if (this.matchesParsedSelector(child, parsed)) {\n results.push(child);\n }\n // Рекурсивно ищем в потомках\n searchInChildren(child);\n }\n };\n \n searchInChildren(root);\n return results;\n }\n\n private static matchesParsedSelector(element: ICSElement, parsed: ParsedCSSSelector): boolean {\n // Проверяем имя элемента\n if (parsed.element && element.name !== parsed.element) {\n return false;\n }\n\n // Проверяем ID\n if (parsed.id && element.getData('id') !== parsed.id) {\n return false;\n }\n\n // Проверяем классы\n if (parsed.classes) {\n const elementClasses = element.getData('class');\n if (!elementClasses) return false;\n const classList = typeof elementClasses === 'string' ? elementClasses.split(' ') : [];\n for (const className of parsed.classes) {\n if (!classList.includes(className)) {\n return false;\n }\n }\n }\n\n // Проверяем атрибуты\n if (parsed.attributes) {\n for (const attr of parsed.attributes) {\n const value = element.getData(attr.name);\n \n\n \n // Если нет оператора, просто проверяем наличие атрибута\n if (!attr.operator) {\n if (value === undefined || value === null) {\n return false;\n }\n continue;\n }\n \n // Если есть оператор, проверяем значение\n if (!value) return false;\n \n if (attr.operator && attr.value) {\n switch (attr.operator) {\n case '=':\n if (value !== attr.value) return false;\n break;\n case '~=':\n if (!value.toString().split(' ').includes(attr.value)) return false;\n break;\n case '|=':\n if (!value.toString().startsWith(attr.value + '-') && value !== attr.value) return false;\n break;\n case '^=':\n if (!value.toString().startsWith(attr.value)) return false;\n break;\n case '$=':\n if (!value.toString().endsWith(attr.value)) return false;\n break;\n case '*=':\n if (!value.toString().includes(attr.value)) return false;\n break;\n }\n }\n }\n }\n\n // Проверяем псевдо-классы\n if (parsed.pseudoClasses) {\n for (const pseudo of parsed.pseudoClasses) {\n if (!this.matchesPseudoClass(element, pseudo)) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n private static matchesPseudoClass(element: ICSElement, pseudo: { name: string; argument?: string }): boolean {\n switch (pseudo.name) {\n case 'first-child':\n return element.index === 0;\n case 'last-child':\n const parent = element.mainOwner;\n return parent ? element.index === parent.elementsCount() - 1 : true;\n case 'nth-child':\n if (pseudo.argument) {\n const n = parseInt(pseudo.argument);\n return element.index === n - 1; // CSS использует 1-based индексы\n }\n return false;\n case 'empty':\n return element.elementsCount() === 0;\n case 'not':\n // Реализация :not() селектора\n if (pseudo.argument) {\n const notParsed = this.parseCSS(pseudo.argument);\n return !this.matchesParsedSelector(element, notParsed);\n }\n return false;\n case 'has':\n // Реализация :has() селектора\n if (pseudo.argument) {\n const hasParsed = this.parseCSS(pseudo.argument);\n // Ищем только среди потомков, не включая сам элемент\n const descendants = this.findDescendantsByParsedSelector(element, hasParsed);\n return descendants.length > 0;\n }\n return false;\n default:\n return false;\n }\n }\n\n private static createXPathContext(root: ICSElement): any {\n // Этот метод больше не нужен, так как мы используем JSPath\n // который работает с JSON-объектами напрямую.\n return { root };\n }\n\n private static evaluateXPath(context: any, expression: string): ICSElement[] {\n const rootElement = context.root as CSElement;\n \n // 1. Создаем карту всех элементов для быстрого доступа по ID\n const elementMap = new Map<string, ICSElement>();\n const traverseAndMap = (element: ICSElement) => {\n elementMap.set(element.id, element);\n element.getAllElements().forEach(child => traverseAndMap(child as CSElement));\n };\n traverseAndMap(rootElement);\n\n // 2. Преобразуем структуру в JSON\n const jsonObject = rootElement.toJSPathObject();\n\n // 3. Применяем JSPath\n let results = JSPath.apply(expression, jsonObject);\n if (results === undefined) {\n results = [];\n } else if (!Array.isArray(results)) {\n results = [results];\n }\n\n // 4. Сопоставляем результаты с реальными элементами CSElement\n const finalElements: ICSElement[] = [];\n const seenIds = new Set<string>();\n\n for (const res of results) {\n if (res && typeof res === 'object' && res.___id) {\n const elementId = res.___id;\n if (!seenIds.has(elementId)) {\n const element = elementMap.get(elementId);\n if (element) {\n finalElements.push(element);\n seenIds.add(elementId);\n }\n }\n }\n }\n \n return finalElements;\n }\n\n private static traverseAndIndex(element: ICSElement, index: ElementIndex, depth: number): void {\n // Добавляем в общий индекс\n index.all.add(element);\n\n // Индексируем по имени\n if (element.name) {\n if (!index.byName.has(element.name)) {\n index.byName.set(element.name, new Set());\n }\n index.byName.get(element.name)!.add(element);\n }\n\n // Индексируем по ID\n const id = element.getData('id');\n if (id) {\n index.byId.set(id, element);\n }\n\n // Индексируем по классам\n const classes = element.getData('class');\n if (classes) {\n const classList = typeof classes === 'string' ? classes.split(' ') : [classes];\n for (const className of classList) {\n if (!index.byClass.has(className)) {\n index.byClass.set(className, new Set());\n }\n index.byClass.get(className)!.add(element);\n }\n }\n\n // Индексируем по атрибутам\n const data = element.data;\n for (const [key, value] of data) {\n if (!index.byAttribute.has(key)) {\n index.byAttribute.set(key, new Map());\n }\n const attrMap = index.byAttribute.get(key)!;\n if (!attrMap.has(value)) {\n attrMap.set(value, new Set());\n }\n attrMap.get(value)!.add(element);\n }\n\n // Индексируем по глубине\n if (!index.byDepth.has(depth)) {\n index.byDepth.set(depth, new Set());\n }\n index.byDepth.get(depth)!.add(element);\n\n // Рекурсивно обрабатываем дочерние элементы\n const children = element.getAllElements() as ICSElement[];\n for (const child of children) {\n this.traverseAndIndex(child, index, depth + 1);\n }\n }\n\n // ===== Methods from old QueryEngine =====\n\n /**\n * Выполняет поиск по объекту селектора\n */\n private static queryBySelector(root: ICSElement, selector: QuerySelector): ICSElement[] {\n const results: ICSElement[] = [];\n this.traverseAndMatchObject(root, selector, results);\n return results;\n }\n\n /**\n * Рекурсивно обходит дерево и собирает подходящие элементы для объектного селектора\n */\n private static traverseAndMatchObject(element: ICSElement, selector: QuerySelector, results: ICSElement[]): void {\n if (this.matchesObjectSelector(element, selector)) {\n results.push(element);\n }\n\n const children = element.getAllElements();\n for (const child of children) {\n this.traverseAndMatchObject(child as ICSElement, selector, results);\n }\n }\n\n /**\n * Проверяет, соответствует ли элемент объектному селектору\n */\n private static matchesObjectSelector(element: ICSElement, selector: QuerySelector): boolean {\n if (selector.name !== undefined && element.name !== selector.name) {\n return false;\n }\n\n if (selector.index !== undefined && element.index !== selector.index) {\n return false;\n }\n\n if (selector.hasData) {\n for (const key of selector.hasData) {\n if (!element.getData(key)) {\n return false;\n }\n }\n }\n\n if (selector.depth !== undefined) {\n const depth = this.getElementDepth(element);\n if (typeof selector.depth === 'number') {\n if (depth !== selector.depth) return false;\n } else {\n const { min, max } = selector.depth;\n if (min !== undefined && depth < min) return false;\n if (max !== undefined && depth > max) return false;\n }\n }\n\n if (selector.custom && !selector.custom(element)) {\n return false;\n }\n\n return true;\n }\n \n /**\n * Вычисляет глубину элемента в дереве\n */\n private static getElementDepth(element: ICSElement): number {\n let depth = 0;\n let current = element.mainOwner;\n while (current) {\n depth++;\n current = current.mainOwner;\n }\n return depth;\n }\n\n /**\n * Создает комплексный селектор для поиска элементов\n */\n static createSelector(): SelectorBuilder {\n return new SelectorBuilder();\n }\n\n // ===== End of methods from old QueryEngine =====\n}\n\n/**\n * Builder для создания сложных селекторов\n */\nexport class SelectorBuilder {\n private selector: QuerySelector = {};\n\n withName(name: string): this {\n this.selector.name = name;\n return this;\n }\n\n withIndex(index: number): this {\n this.selector.index = index;\n return this;\n }\n\n withData(...keys: string[]): this {\n this.selector.hasData = [...(this.selector.hasData || []), ...keys];\n return this;\n }\n\n withDepth(depth: number | { min?: number; max?: number }): this {\n this.selector.depth = depth;\n return this;\n }\n\n withCustom(predicate: (element: ICSElement) => boolean): this {\n const existing = this.selector.custom;\n if (existing) {\n this.selector.custom = (element) => existing(element) && predicate(element);\n } else {\n this.selector.custom = predicate;\n }\n return this;\n }\n\n build(): QuerySelector {\n return { ...this.selector };\n }\n}\n\n/**\n * Предопределенные селекторы\n */\nexport const CommonSelectors = {\n byName: (name: string): QuerySelector => ({ name }),\n byIndex: (index: number): QuerySelector => ({ index }),\n leaves: (): QuerySelector => ({\n custom: (element) => element.elementsCount() === 0\n }),\n roots: (): QuerySelector => ({\n custom: (element) => element.mainOwner === null\n }),\n withChildrenCount: (count: number): QuerySelector => ({\n custom: (element) => element.elementsCount() === count\n }),\n atDepth: (depth: number): QuerySelector => ({ depth }),\n withDataType: (key: string, type: string): QuerySelector => ({\n custom: (element) => typeof element.getData(key) === type\n })\n}; ","/**\n * Реализация менеджера Live queries (реактивных запросов)\n */\n\nimport { EventEmitter } from 'eventemitter3';\nimport { CSElement } from '../core/CSElement';\nimport { generateId } from '../utils/helpers';\nimport {\n LiveQueryManager,\n LiveQuery,\n LiveQueryConfig,\n LiveQueryStats,\n LiveQuerySubscription,\n LiveQueryEvent,\n LiveQueryBuilder,\n LiveQueryIndex,\n LiveQueryCallback,\n LiveQueryErrorCallback,\n LiveQueryFilter,\n LiveQueryTransform,\n LiveQuerySort,\n QueryOptions\n} from '../types/live-query-interfaces';\nimport { QueryEngine } from '../utils/QueryEngine';\nimport { ICSElement } from '../types/interfaces';\n\nexport class LiveQueryManagerImpl extends EventEmitter implements LiveQueryManager {\n private queries = new Map<string, LiveQuery>();\n private subscriptions = new Map<string, Map<string, LiveQuerySubscription>>();\n private stats: LiveQueryStats;\n private debounceTimers = new Map<string, NodeJS.Timeout>();\n private updateBatches = new Map<string, Set<string>>();\n private indexes = new Map<string, LiveQueryIndex>();\n private CSElementClass: typeof CSElement | null = null;\n\n constructor() {\n super();\n this.stats = this.createEmptyStats();\n }\n\n public setCSElementClass(cls: typeof CSElement): void {\n this.CSElementClass = cls;\n }\n\n createLiveQuery<T = any>(\n selector: string,\n options: QueryOptions = {},\n config: LiveQueryConfig = {}\n ): LiveQuery<T> {\n const id = generateId();\n \n const query: LiveQuery<T> = {\n id,\n selector,\n options,\n config: {\n autoStart: true,\n debounce: 100,\n cache: true,\n cacheTTL: 5000,\n deep: false,\n batch: true,\n ...config\n },\n results: [],\n active: false,\n lastUpdated: 0,\n updateCount: 0,\n watchedElements: new Set(),\n filters: [],\n transforms: [],\n sort: undefined,\n onResults: undefined,\n onError: undefined\n };\n\n this.queries.set(id, query);\n this.subscriptions.set(id, new Map());\n this.stats.totalQueries++;\n\n // Обновляем статистику селекторов\n this.updateSelectorStats(selector);\n\n // Автоматический запуск если включен\n if (query.config.autoStart) {\n this.start(id);\n }\n\n this.emit('query-created', { queryId: id, query });\n this.emitEvent({\n type: 'query-created',\n queryId: id,\n data: { selector, options, config },\n timestamp: Date.now()\n });\n\n return query;\n }\n\n start(queryId: string): void {\n const query = this.queries.get(queryId);\n if (!query || query.active) {\n return;\n }\n\n query.active = true;\n this.stats.activeQueries++;\n\n // Выполняем первоначальный запрос\n this.executeQuery(queryId);\n\n // Настраиваем наблюдение за изменениями\n this.setupQueryWatching(query);\n\n this.emitEvent({\n type: 'query-started',\n queryId,\n timestamp: Date.now()\n });\n }\n\n stop(queryId: string): void {\n const query = this.queries.get(queryId);\n if (!query || !query.active) {\n return;\n }\n\n query.active = false;\n this.stats.activeQueries--;\n\n // Останавливаем наблюдение\n this.stopQueryWatching(query);\n\n // Очищаем таймеры debounce\n const timer = this.debounceTimers.get(queryId);\n if (timer) {\n clearTimeout(timer);\n this.debounceTimers.delete(queryId);\n }\n\n this.emitEvent({\n type: 'query-stopped',\n queryId,\n timestamp: Date.now()\n });\n }\n\n getLiveQuery(queryId: string): LiveQuery | undefined {\n return this.queries.get(queryId);\n }\n\n getAllLiveQueries(): LiveQuery[] {\n return Array.from(this.queries.values());\n }\n\n removeLiveQuery(queryId: string): boolean {\n const query = this.queries.get(queryId);\n if (!query) {\n return false;\n }\n\n // Останавливаем запрос\n if (query.active) {\n this.stop(queryId);\n }\n\n // Удаляем все подписки\n this.subscriptions.delete(queryId);\n\n // Удаляем запрос\n this.queries.delete(queryId);\n this.stats.totalQueries--;\n\n this.emitEvent({\n type: 'query-removed',\n queryId,\n timestamp: Date.now()\n });\n\n return true;\n }\n\n updateQuery(queryId: string): void {\n const query = this.queries.get(queryId);\n if (!query || !query.active) {\n return;\n }\n\n if (query.config.debounce && query.config.debounce > 0) {\n // Используем debounce\n const existingTimer = this.debounceTimers.get(queryId);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n\n const timer = setTimeout(() => {\n this.executeQuery(queryId);\n this.debounceTimers.delete(queryId);\n }, query.config.debounce);\n\n this.debounceTimers.set(queryId, timer);\n } else {\n // Немедленное обновление\n this.executeQuery(queryId);\n }\n }\n\n updateAllQueries(): void {\n for (const [queryId, query] of this.queries) {\n if (query.active) {\n this.updateQuery(queryId);\n }\n }\n }\n\n addFilter<T>(queryId: string, filter: LiveQueryFilter<T>): void {\n const query = this.queries.get(queryId);\n if (query) {\n query.filters.push(filter);\n this.updateQuery(queryId);\n }\n }\n\n removeFilter(queryId: string, filterIndex: number): void {\n const query = this.queries.get(queryId);\n if (query && filterIndex >= 0 && filterIndex < query.filters.length) {\n query.filters.splice(filterIndex, 1);\n this.updateQuery(queryId);\n }\n }\n\n addTransform<T, R>(queryId: string, transform: LiveQueryTransform<T, R>): void {\n const query = this.queries.get(queryId);\n if (query) {\n query.transforms.push(transform);\n this.updateQuery(queryId);\n }\n }\n\n setSort<T>(queryId: string, sort: LiveQuerySort<T>): void {\n const query = this.queries.get(queryId);\n if (query) {\n query.sort = sort;\n this.updateQuery(queryId);\n }\n }\n\n subscribe<T>(queryId: string, callback: LiveQueryCallback<T>): string {\n const subscriptionId = generateId();\n const subscriptions = this.subscriptions.get(queryId);\n \n if (subscriptions) {\n const subscription: LiveQuerySubscription = {\n id: subscriptionId,\n queryId,\n callback,\n active: true,\n createdAt: Date.now()\n };\n\n subscriptions.set(subscriptionId, subscription);\n }\n\n return subscriptionId;\n }\n\n unsubscribe(queryId: string, subscriptionId: string): boolean {\n const subscriptions = this.subscriptions.get(queryId);\n if (subscriptions) {\n return subscriptions.delete(subscriptionId);\n }\n return false;\n }\n\n getStats(): LiveQueryStats {\n // Обновляем актуальную статистику\n this.updateStats();\n return { ...this.stats };\n }\n\n clear(): void {\n // Останавливаем все активные запросы\n for (const [queryId, query] of this.queries) {\n if (query.active) {\n this.stop(queryId);\n }\n }\n\n // Очищаем все данные\n this.queries.clear();\n this.subscriptions.clear();\n this.debounceTimers.clear();\n this.updateBatches.clear();\n this.indexes.clear();\n\n // Сбрасываем статистику\n this.stats = this.createEmptyStats();\n }\n\n notifyElementChange(element: CSElement, _changeType: 'create' | 'update' | 'delete'): void {\n // Находим все запросы, которые могут быть затронуты этим изменением\n const affectedQueries = this.findAffectedQueries(element);\n\n for (const queryId of affectedQueries) {\n this.updateQuery(queryId);\n }\n }\n\n notifyDataChange(element: CSElement, key: string, _newValue: any, _oldValue: any): void {\n // Находим запросы, которые используют этот ключ данных\n const affectedQueries = this.findQueriesUsingDataKey(element, key);\n\n for (const queryId of affectedQueries) {\n this.updateQuery(queryId);\n }\n }\n\n // Приватные методы\n\n private async executeQuery(queryId: string): Promise<void> {\n const query = this.queries.get(queryId);\n if (!query) {\n return;\n }\n\n const startTime = Date.now();\n\n try {\n // Выполняем базовый запрос\n let results = await this.performQuery(query);\n\n // Применяем фильтры\n for (const filter of query.filters) {\n results = results.filter(filter);\n }\n\n // Применяем трансформации\n for (const transform of query.transforms) {\n results = results.map(transform);\n }\n\n // Применяем сортировку\n if (query.sort) {\n results.sort(query.sort);\n }\n\n // Применяем лимит\n if (query.config.limit && query.config.limit > 0) {\n results = results.slice(0, query.config.limit);\n }\n\n // Обновляем результаты\n const oldResults = query.results;\n query.results = results;\n query.lastUpdated = Date.now();\n query.updateCount++;\n\n // Обновляем статистику\n this.stats.totalUpdates++;\n const executionTime = Date.now() - startTime;\n this.updateExecutionTimeStats(executionTime);\n\n // Уведомляем подписчиков\n this.notifySubscribers(queryId, results, query);\n\n // Вызываем callback если установлен\n if (query.onResults) {\n query.onResults(results, query);\n }\n\n this.emitEvent({\n type: 'query-updated',\n queryId,\n data: { \n results, \n oldResults, \n executionTime,\n changeCount: this.calculateChangeCount(oldResults, results)\n },\n timestamp: Date.now()\n });\n\n this.emitEvent({\n type: 'results-changed',\n queryId,\n data: { results, executionTime },\n timestamp: Date.now()\n });\n\n } catch (error) {\n // Обработка ошибок\n if (query.onError) {\n query.onError(error as Error, query);\n }\n\n this.emit('query-error', { queryId, error });\n console.error(`Live query ${queryId} error:`, error);\n }\n }\n\n private async performQuery(query: LiveQuery): Promise<any[]> {\n if (!this.CSElementClass) {\n throw new Error('CSElementClass has not been set on LiveQueryManager.');\n }\n try {\n const root = query.options?.root;\n if (!root) {\n // Fallback to searching all elements if no root is provided.\n const allElements = this.CSElementClass.getAllElements();\n const results: ICSElement[] = [];\n allElements.forEach((el: ICSElement) => {\n results.push(...QueryEngine.query(el, query.selector));\n });\n return Array.from(new Set(results)); // Remove duplicates\n }\n \n // Если есть корневой элемент, ищем только в его контексте\n return QueryEngine.query(root, query.selector);\n } catch (error) {\n console.warn('Error executing query:', error);\n return [];\n }\n }\n\n private setupQueryWatching(_query: LiveQuery): void {\n // Эта логика теперь полностью управляется через\n // вызовы notifyElementChange и notifyDataChange,\n // которые инициируют updateQuery.\n // Использование computed здесь было бы некорректным,\n // так как система реактивности не отслеживает\n // добавление/удаление дочерних элементов напрямую.\n }\n\n private stopQueryWatching(_query: LiveQuery): void {\n // Останавливаем наблюдение за элементами\n // Это будет реализовано при интеграции с ReactivityManager\n }\n\n private findAffectedQueries(element: CSElement): string[] {\n const affected: string[] = [];\n\n for (const [queryId, query] of this.queries) {\n if (query.active && this.queryAffectedByElement(query, element)) {\n affected.push(queryId);\n }\n }\n\n return affected;\n }\n\n private findQueriesUsingDataKey(_element: CSElement, key: string): string[] {\n const affected: string[] = [];\n\n for (const [queryId, query] of this.queries) {\n if (query.active && this.queryUsesDataKey(query, key)) {\n affected.push(queryId);\n }\n }\n\n return affected;\n }\n\n private queryAffectedByElement(query: LiveQuery, element: CSElement): boolean {\n // Проверяем, может ли изменение элемента повлиять на результаты запроса\n // Это упрощенная логика, в реальности будет более сложная\n return query.watchedElements.has(element.id) || \n query.selector.includes(element.name) ||\n query.selector.includes('*');\n }\n\n private queryUsesDataKey(query: LiveQuery, key: string): boolean {\n // Проверяем, использует ли запрос определенный ключ данных\n return query.selector.includes(`[${key}]`) || \n query.selector.includes(`data-${key}`) ||\n query.selector.includes(`@${key}`);\n }\n\n private notifySubscribers(queryId: string, results: any[], query: LiveQuery): void {\n const subscriptions = this.subscriptions.get(queryId);\n if (!subscriptions) {\n return;\n }\n\n for (const subscription of subscriptions.values()) {\n if (subscription.active) {\n try {\n subscription.callback(results, query);\n } catch (error) {\n console.error(`Subscription callback error for query ${queryId}:`, error);\n }\n }\n }\n }\n\n private calculateChangeCount(oldResults: any[], newResults: any[]): number {\n // Простой подсчет изменений\n if (oldResults.length !== newResults.length) {\n