UNPKG

cs-element

Version:

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

851 lines (845 loc) 34.8 kB
'use strict'; var pako = require('pako'); var lz4 = require('lz4js'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var pako__namespace = /*#__PURE__*/_interopNamespaceDefault(pako); var lz4__namespace = /*#__PURE__*/_interopNamespaceDefault(lz4); // Типы сжатия var CompressionAlgorithm; (function (CompressionAlgorithm) { CompressionAlgorithm["GZIP"] = "gzip"; CompressionAlgorithm["DEFLATE"] = "deflate"; CompressionAlgorithm["BROTLI"] = "brotli"; CompressionAlgorithm["LZ4"] = "lz4"; CompressionAlgorithm["ZSTD"] = "zstd"; CompressionAlgorithm["SNAPPY"] = "snappy"; })(CompressionAlgorithm || (CompressionAlgorithm = {})); var CompressionLevel; (function (CompressionLevel) { CompressionLevel[CompressionLevel["FASTEST"] = 1] = "FASTEST"; CompressionLevel[CompressionLevel["FAST"] = 3] = "FAST"; CompressionLevel[CompressionLevel["BALANCED"] = 6] = "BALANCED"; CompressionLevel[CompressionLevel["BEST"] = 9] = "BEST"; })(CompressionLevel || (CompressionLevel = {})); var CompressionMode; (function (CompressionMode) { CompressionMode["AUTOMATIC"] = "automatic"; CompressionMode["MANUAL"] = "manual"; CompressionMode["THRESHOLD_BASED"] = "threshold_based"; CompressionMode["SELECTIVE"] = "selective"; })(CompressionMode || (CompressionMode = {})); var DataType; (function (DataType) { DataType["JSON"] = "json"; DataType["TEXT"] = "text"; DataType["BINARY"] = "binary"; DataType["IMAGE"] = "image"; DataType["AUDIO"] = "audio"; DataType["VIDEO"] = "video"; })(DataType || (DataType = {})); class CompressionPlugin { constructor(config = {}) { this.name = 'CompressionPlugin'; this.version = '1.0.0'; this.description = 'Плагин для сжатия данных и оптимизации памяти'; this.rules = new Map(); this.profiles = new Map(); this.jobs = new Map(); this.cache = new Map(); this.compressedElements = new Map(); this.config = { enabled: true, priority: 150, defaultAlgorithm: CompressionAlgorithm.GZIP, defaultLevel: CompressionLevel.BALANCED, mode: CompressionMode.THRESHOLD_BASED, autoCompress: true, compressionThreshold: 1024, // 1KB maxCompressionJobs: 5, enableCaching: true, cacheSize: 50 * 1024 * 1024, // 50MB cacheTTL: 3600000, // 1 час enableBenchmarking: false, validateChecksums: true, enableStatistics: true, blacklistedDataTypes: [], whitelistedDataTypes: [], ...config }; this.stats = { totalCompressions: 0, totalDecompressions: 0, totalOriginalSize: 0, totalCompressedSize: 0, averageCompressionRatio: 0, averageCompressionTime: 0, averageDecompressionTime: 0, compressionsByAlgorithm: {}, compressionsByLevel: {}, cacheHits: 0, cacheMisses: 0, cacheHitRatio: 0, activeJobs: 0, failedCompressions: 0, savedSpace: 0, bestCompressionRatio: 0, worstCompressionRatio: 1 }; // Инициализация счетчиков Object.values(CompressionAlgorithm).forEach(algorithm => { this.stats.compressionsByAlgorithm[algorithm] = 0; }); Object.values(CompressionLevel).forEach(level => { this.stats.compressionsByLevel[level] = 0; }); this.initializeDefaultProfiles(); this.initializeDefaultRules(); } install(_CSElementClass) { // Регистрация плагина в системе CSElement // В реальной реализации здесь была бы интеграция с жизненным циклом элементов console.log(`${this.name} установлен для CSElement`); } async initialize() { console.log(`Инициализация ${this.name} v${this.version}`); // Очистка кэша по расписанию if (this.config.enableCaching) { setInterval(() => { this.cleanupCache(); }, 60000); // каждую минуту } // Обновление статистики if (this.config.enableStatistics) { setInterval(() => { this.updateStats(); }, 30000); // каждые 30 секунд } } async destroy() { // Завершение активных задач for (const job of this.jobs.values()) { if (job.status === 'processing') { job.status = 'failed'; job.error = 'Plugin shutdown'; job.endTime = new Date(); } } this.cache.clear(); console.log(`${this.name} деинициализирован`); } getConfig() { return { ...this.config }; } updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } // Lifecycle hooks async afterCreate(element, _context) { if (this.config.autoCompress && this.shouldCompress(element)) { await this.compressElement(element); } return { success: true }; } async afterUpdate(element, _context) { if (this.config.autoCompress && this.shouldCompress(element)) { await this.compressElement(element); } return { success: true }; } async beforeRead(element, _context) { if (this.isElementCompressed(element)) { await this.decompressElement(element); } return { success: true }; } // Основные методы сжатия async compressData(data, options = {}) { const startTime = Date.now(); const inputBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8'); const originalSize = inputBuffer.length; const compressionOptions = { algorithm: this.config.defaultAlgorithm, level: this.config.defaultLevel, validateChecksum: this.config.validateChecksums, ...options }; // Проверка кэша const cacheKey = this.generateCacheKey(inputBuffer, compressionOptions); if (this.config.enableCaching) { const cached = this.getCachedCompression(cacheKey); if (cached) { this.stats.cacheHits++; return this.createResultFromCache(cached, originalSize, startTime); } this.stats.cacheMisses++; } try { // Выполнение сжатия const compressedData = await this.performCompression(inputBuffer, compressionOptions); const compressedSize = compressedData.length; const compressionRatio = compressedSize / originalSize; const duration = Date.now() - startTime; // Вычисление контрольной суммы const checksum = this.calculateChecksum(compressedData); const result = { originalSize, compressedSize, compressionRatio, algorithm: compressionOptions.algorithm, level: compressionOptions.level, duration, checksum, metadata: { inputType: Buffer.isBuffer(data) ? 'buffer' : 'string', timestamp: new Date().toISOString() } }; // Сохранение в кэш if (this.config.enableCaching) { this.cacheCompression(cacheKey, compressedData, compressionOptions, checksum); } // Обновление статистики this.updateCompressionStats(result); return result; } catch (error) { this.stats.failedCompressions++; throw new Error(`Ошибка сжатия: ${error instanceof Error ? error.message : String(error)}`); } } async decompressData(compressedData, algorithm, expectedChecksum) { const startTime = Date.now(); try { // Валидация контрольной суммы if (expectedChecksum && this.config.validateChecksums) { const actualChecksum = this.calculateChecksum(compressedData); if (actualChecksum !== expectedChecksum) { throw new Error('Нарушена целостность сжатых данных'); } } // Выполнение декомпрессии const decompressedData = await this.performDecompression(compressedData, algorithm); const duration = Date.now() - startTime; // Обновление статистики this.stats.totalDecompressions++; this.stats.averageDecompressionTime = (this.stats.averageDecompressionTime + duration) / 2; return decompressedData; } catch (error) { throw new Error(`Ошибка декомпрессии: ${error instanceof Error ? error.message : String(error)}`); } } async compressElement(element) { if (!this.shouldCompress(element)) { return null; } const jobId = this.generateId(); const serializedData = this.serializeElementForCompression(element); const dataBuffer = Buffer.from(serializedData, 'utf8'); const job = { id: jobId, elementId: element.id, status: 'pending', algorithm: this.config.defaultAlgorithm, level: this.config.defaultLevel, startTime: new Date(), originalSize: dataBuffer.length }; this.jobs.set(jobId, job); this.stats.activeJobs++; try { job.status = 'processing'; // Выбор алгоритма и уровня сжатия const rule = this.findMatchingRule(element); const algorithm = rule?.algorithm || this.config.defaultAlgorithm; const level = rule?.level || this.config.defaultLevel; const options = { algorithm, level, validateChecksum: this.config.validateChecksums, ...rule?.options }; // Сжатие данных const result = await this.compressData(dataBuffer, options); // Сохранение результата в элементе element.setData('_compressed', true); element.setData('_compressionResult', result); element.setData('_originalData', serializedData); job.status = 'completed'; job.endTime = new Date(); job.compressedSize = result.compressedSize; job.result = result; this.compressedElements.set(element.id, result); console.log(`Элемент ${element.id} сжат: ${result.originalSize} -> ${result.compressedSize} байт (${(result.compressionRatio * 100).toFixed(1)}%)`); return result; } catch (error) { job.status = 'failed'; job.error = error instanceof Error ? error.message : String(error); job.endTime = new Date(); console.error(`Ошибка сжатия элемента ${element.id}:`, error); return null; } finally { this.stats.activeJobs--; // Удаление задачи через 5 минут setTimeout(() => { this.jobs.delete(jobId); }, 300000); } } async decompressElement(element) { if (!this.isElementCompressed(element)) { return true; } try { const compressionResult = element.getData('_compressionResult'); const originalData = element.getData('_originalData'); if (!compressionResult || !originalData) { console.error(`Отсутствуют данные для декомпрессии элемента ${element.id}`); return false; } // Восстановление оригинальных данных const parsedData = JSON.parse(originalData); // Восстановление данных элемента Object.keys(parsedData).forEach(key => { if (!key.startsWith('_compression')) { element.setData(key, parsedData[key]); } }); // Удаление данных сжатия element.setData('_compressed', false); element.setData('_compressionResult', undefined); element.setData('_originalData', undefined); this.compressedElements.delete(element.id); console.log(`Элемент ${element.id} декомпрессирован`); return true; } catch (error) { console.error(`Ошибка декомпрессии элемента ${element.id}:`, error); return false; } } // Управление профилями createProfile(profileData) { const profile = { id: this.generateId(), ...profileData }; this.profiles.set(profile.id, profile); return profile; } getProfile(id) { return this.profiles.get(id); } updateProfile(id, updates) { const profile = this.profiles.get(id); if (!profile) return false; Object.assign(profile, updates); return true; } deleteProfile(id) { return this.profiles.delete(id); } // Управление правилами addRule(ruleData) { const rule = { id: this.generateId(), createdAt: new Date(), updatedAt: new Date(), ...ruleData }; this.rules.set(rule.id, rule); return rule; } getRule(id) { return this.rules.get(id); } updateRule(id, updates) { const rule = this.rules.get(id); if (!rule) return false; Object.assign(rule, { ...updates, updatedAt: new Date() }); return true; } deleteRule(id) { return this.rules.delete(id); } // Бенчмаркинг async runBenchmark(data, algorithms = Object.values(CompressionAlgorithm), levels = Object.values(CompressionLevel)) { if (!this.config.enableBenchmarking) { throw new Error('Бенчмаркинг отключен'); } const results = []; const dataType = this.detectDataType(data); for (const algorithm of algorithms) { for (const level of levels) { try { const compressionStart = Date.now(); const compressionResult = await this.compressData(data, { algorithm, level }); const compressionTime = Date.now() - compressionStart; const decompressionStart = Date.now(); await this.decompressData(Buffer.from('compressed_data'), // заглушка algorithm); const decompressionTime = Date.now() - decompressionStart; const result = { dataSize: data.length, dataType, algorithm, level, compressionRatio: compressionResult.compressionRatio, compressionTime, decompressionTime, memoryUsage: process.memoryUsage().heapUsed, timestamp: new Date() }; results.push(result); } catch (error) { console.error(`Ошибка бенчмарка ${algorithm}/${level}:`, error); } } } return results.sort((a, b) => a.compressionRatio - b.compressionRatio); } // Анализ и оптимизация async analyzeBestAlgorithm(data) { const benchmarkResults = await this.runBenchmark(data); if (benchmarkResults.length === 0) { return this.config.defaultAlgorithm; } // Выбор лучшего алгоритма по соотношению сжатия и скорости const scored = benchmarkResults.map(result => ({ algorithm: result.algorithm, score: (1 - result.compressionRatio) * 0.7 + (1 / result.compressionTime) * 0.3 })); scored.sort((a, b) => b.score - a.score); return scored[0].algorithm; } getCompressionRecommendations(element) { const dataSize = this.serializeElementForCompression(element).length; const dataType = this.detectElementDataType(element); // Рекомендации на основе типа и размера данных if (dataSize < 1024) { return { algorithm: CompressionAlgorithm.LZ4, level: CompressionLevel.FAST, expectedRatio: 0.8, reason: 'Малый размер данных - приоритет скорости' }; } if (dataType === DataType.TEXT || dataType === DataType.JSON) { return { algorithm: CompressionAlgorithm.BROTLI, level: CompressionLevel.BALANCED, expectedRatio: 0.4, reason: 'Текстовые данные хорошо сжимаются Brotli' }; } return { algorithm: this.config.defaultAlgorithm, level: this.config.defaultLevel, expectedRatio: 0.6, reason: 'Стандартные настройки' }; } // Утилиты /** * Сериализация элемента для сжатия с использованием встроенной сериализации */ serializeElementForCompression(element) { // Используем встроенную сериализацию CSElement const serialized = element.serialize({ includeChildren: true, includeData: true, includeMetadata: true }); return JSON.stringify(serialized); } shouldCompress(element) { if (!this.config.autoCompress) return false; const serializedData = this.serializeElementForCompression(element); const dataSize = Buffer.byteLength(serializedData, 'utf8'); // Проверка минимального размера if (dataSize < this.config.compressionThreshold) { return false; } // Проверка лимита задач if (this.stats.activeJobs >= this.config.maxCompressionJobs) { return false; } // Проверка типов данных const dataType = this.detectElementDataType(element); if (this.config.blacklistedDataTypes.includes(dataType)) { return false; } if (this.config.whitelistedDataTypes.length > 0 && !this.config.whitelistedDataTypes.includes(dataType)) { return false; } return true; } isElementCompressed(element) { return element.getData('_compressed') === true; } findMatchingRule(element) { const rules = Array.from(this.rules.values()) .filter(rule => rule.enabled) .sort((a, b) => b.priority - a.priority); for (const rule of rules) { if (this.evaluateRuleConditions(rule, element)) { return rule; } } return undefined; } evaluateRuleConditions(rule, element) { return rule.conditions.every(condition => { switch (condition.type) { case 'size': const size = this.serializeElementForCompression(element).length; return this.evaluateCondition(size, condition.operator, condition.value); case 'elementType': const elementType = element.constructor.name; return this.evaluateCondition(elementType, condition.operator, condition.value); case 'dataType': const dataType = this.detectElementDataType(element); return this.evaluateCondition(dataType, condition.operator, condition.value); default: return true; } }); } evaluateCondition(actual, operator, expected) { switch (operator) { case 'equals': return actual === expected; case 'greater': return actual > expected; case 'less': return actual < expected; case 'contains': return String(actual).includes(String(expected)); case 'matches': return new RegExp(expected).test(String(actual)); default: return true; } } detectElementDataType(element) { const serialized = this.serializeElementForCompression(element); // Простая эвристика определения типа данных if (serialized.includes('"image"') || serialized.includes('data:image/')) { return DataType.IMAGE; } if (serialized.includes('"audio"') || serialized.includes('data:audio/')) { return DataType.AUDIO; } if (serialized.includes('"video"') || serialized.includes('data:video/')) { return DataType.VIDEO; } try { JSON.parse(serialized); return DataType.JSON; } catch { return DataType.TEXT; } } detectDataType(data) { // Проверка магических байтов для определения типа const header = data.slice(0, 16); // JPEG if (header[0] === 0xFF && header[1] === 0xD8) return DataType.IMAGE; // PNG if (header.slice(0, 8).equals(Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]))) return DataType.IMAGE; // PDF if (header.slice(0, 4).equals(Buffer.from('%PDF'))) return DataType.BINARY; // Проверка на текст const text = data.toString('utf8', 0, Math.min(1024, data.length)); try { JSON.parse(text); return DataType.JSON; } catch { // Проверка на читаемый текст const printableChars = text.replace(/[^\x20-\x7E]/g, '').length; const ratio = printableChars / text.length; return ratio > 0.8 ? DataType.TEXT : DataType.BINARY; } } async performCompression(data, options) { try { switch (options.algorithm) { case CompressionAlgorithm.GZIP: { const level = Math.max(0, Math.min(9, options.level)); const compressed = pako__namespace.gzip(data, { level }); return Buffer.from(compressed); } case CompressionAlgorithm.DEFLATE: { const level = Math.max(0, Math.min(9, options.level)); const compressed = pako__namespace.deflate(data, { level }); return Buffer.from(compressed); } case CompressionAlgorithm.LZ4: { const compressed = lz4__namespace.compress(data); return Buffer.from(compressed); } case CompressionAlgorithm.BROTLI: // Brotli требует дополнительной настройки, пока используем gzip как fallback console.warn('Brotli сжатие пока не поддерживается, используется GZIP'); const gzipLevel = Math.max(0, Math.min(9, options.level)); const gzipCompressed = pako__namespace.gzip(data, { level: gzipLevel }); return Buffer.from(gzipCompressed); default: throw new Error(`Неподдерживаемый алгоритм сжатия: ${options.algorithm}`); } } catch (error) { console.error('Ошибка сжатия данных:', error); // В случае ошибки возвращаем исходные данные return data; } } async performDecompression(data, algorithm) { try { switch (algorithm) { case CompressionAlgorithm.GZIP: { const decompressed = pako__namespace.ungzip(data); return Buffer.from(decompressed); } case CompressionAlgorithm.DEFLATE: { const decompressed = pako__namespace.inflate(data); return Buffer.from(decompressed); } case CompressionAlgorithm.LZ4: { const decompressed = lz4__namespace.decompress(data); return Buffer.from(decompressed); } case CompressionAlgorithm.BROTLI: // Brotli декомпрессия пока не поддерживается, используем gzip как fallback console.warn('Brotli декомпрессия пока не поддерживается, используется GZIP'); const gzipDecompressed = pako__namespace.ungzip(data); return Buffer.from(gzipDecompressed); default: throw new Error(`Неподдерживаемый алгоритм декомпрессии: ${algorithm}`); } } catch (error) { console.error('Ошибка декомпрессии данных:', error); // В случае ошибки возвращаем исходные данные return data; } } generateCacheKey(data, options) { const dataHash = this.calculateChecksum(data); const optionsHash = this.calculateChecksum(Buffer.from(JSON.stringify(options))); return `${dataHash}-${optionsHash}`; } getCachedCompression(key) { const cached = this.cache.get(key); if (!cached) return undefined; // Проверка TTL if (Date.now() - cached.createdAt.getTime() > this.config.cacheTTL) { this.cache.delete(key); return undefined; } cached.lastAccessed = new Date(); cached.accessCount++; return cached; } cacheCompression(key, data, options, checksum) { // Проверка лимита размера кэша if (this.getCurrentCacheSize() + data.length > this.config.cacheSize) { this.evictLeastRecentlyUsed(); } const cacheEntry = { key, data, algorithm: options.algorithm, level: options.level, checksum, createdAt: new Date(), lastAccessed: new Date(), accessCount: 1, size: data.length }; this.cache.set(key, cacheEntry); } createResultFromCache(cached, originalSize, startTime) { return { originalSize, compressedSize: cached.size, compressionRatio: cached.size / originalSize, algorithm: cached.algorithm, level: cached.level, duration: Date.now() - startTime, checksum: cached.checksum, metadata: { fromCache: true, cacheHit: true } }; } cleanupCache() { const now = Date.now(); const toDelete = []; for (const [key, entry] of this.cache.entries()) { if (now - entry.createdAt.getTime() > this.config.cacheTTL) { toDelete.push(key); } } toDelete.forEach(key => this.cache.delete(key)); if (toDelete.length > 0) { console.log(`Удалено ${toDelete.length} устаревших записей из кэша сжатия`); } } evictLeastRecentlyUsed() { const entries = Array.from(this.cache.entries()); entries.sort((a, b) => a[1].lastAccessed.getTime() - b[1].lastAccessed.getTime()); // Удаляем 25% наименее используемых записей const toEvict = Math.ceil(entries.length * 0.25); for (let i = 0; i < toEvict; i++) { this.cache.delete(entries[i][0]); } } getCurrentCacheSize() { return Array.from(this.cache.values()).reduce((total, entry) => total + entry.size, 0); } updateCompressionStats(result) { this.stats.totalCompressions++; this.stats.totalOriginalSize += result.originalSize; this.stats.totalCompressedSize += result.compressedSize; this.stats.savedSpace += (result.originalSize - result.compressedSize); this.stats.averageCompressionRatio = this.stats.totalCompressedSize / this.stats.totalOriginalSize; this.stats.averageCompressionTime = (this.stats.averageCompressionTime + result.duration) / 2; this.stats.compressionsByAlgorithm[result.algorithm]++; this.stats.compressionsByLevel[result.level]++; this.stats.lastCompressionTime = new Date(); if (result.compressionRatio > this.stats.bestCompressionRatio) { this.stats.bestCompressionRatio = result.compressionRatio; } if (result.compressionRatio < this.stats.worstCompressionRatio) { this.stats.worstCompressionRatio = result.compressionRatio; } } updateStats() { if (this.stats.cacheHits + this.stats.cacheMisses > 0) { this.stats.cacheHitRatio = this.stats.cacheHits / (this.stats.cacheHits + this.stats.cacheMisses); } } initializeDefaultProfiles() { // Профиль для текстовых данных this.createProfile({ name: 'Text Optimized', description: 'Оптимизирован для текстовых данных', dataTypes: [DataType.TEXT, DataType.JSON], algorithm: CompressionAlgorithm.BROTLI, level: CompressionLevel.BALANCED, options: { algorithm: CompressionAlgorithm.BROTLI, level: CompressionLevel.BALANCED, validateChecksum: true }, isDefault: true }); // Профиль для бинарных данных this.createProfile({ name: 'Binary Optimized', description: 'Оптимизирован для бинарных данных', dataTypes: [DataType.BINARY, DataType.IMAGE], algorithm: CompressionAlgorithm.LZ4, level: CompressionLevel.FAST, options: { algorithm: CompressionAlgorithm.LZ4, level: CompressionLevel.FAST, validateChecksum: true }, isDefault: false }); } initializeDefaultRules() { // Правило для больших данных this.addRule({ name: 'Large Data', description: 'Максимальное сжатие для больших данных', enabled: true, priority: 100, conditions: [{ type: 'size', operator: 'greater', value: 1024 * 1024 // 1MB }], algorithm: CompressionAlgorithm.BROTLI, level: CompressionLevel.BEST }); // Правило для быстрого сжатия this.addRule({ name: 'Fast Compression', description: 'Быстрое сжатие для небольших данных', enabled: true, priority: 50, conditions: [{ type: 'size', operator: 'less', value: 10240 // 10KB }], algorithm: CompressionAlgorithm.LZ4, level: CompressionLevel.FASTEST }); } calculateChecksum(data) { // Простая контрольная сумма let hash = 0; for (let i = 0; i < data.length; i++) { hash = ((hash << 5) - hash) + data[i]; hash = hash & hash; } return hash.toString(16); } generateId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } // Публичные методы для получения данных getStats() { return { ...this.stats }; } getAllProfiles() { return Array.from(this.profiles.values()); } getAllRules() { return Array.from(this.rules.values()); } getActiveJobs() { return Array.from(this.jobs.values()).filter(job => job.status === 'processing'); } getCompressedElements() { return new Map(this.compressedElements); } getCacheInfo() { return { size: this.cache.size, entries: this.cache.size, hitRatio: this.stats.cacheHitRatio, totalSize: this.getCurrentCacheSize() }; } } exports.CompressionPlugin = CompressionPlugin; //# sourceMappingURL=compression.js.map